TDD — Tab Perfil na EBD
Status
Implementado
Contexto
Aba "Perfil na EBD" do ProfileDialog. Cargo desejado, dropdown de papéis eligíveis, solicitações de cargo, aprovações pendentes.
Contratos
- No BD e na API, active_role e primary_role guardam apenas roleId; a descrição do cargo é usada só para apresentação em tela.
USER_ROLES_AVAILABLE_LIST(gateway) — catálogo de papéis por unitId + requesterRoleUSER_ROLE_REQUEST— solicitar novo cargoUSER_ROLE_REQUEST_APPROVE/USER_ROLE_REQUEST_REJECT— aprovar/rejeitarUSER_ROLE_REQUEST_DELETE— cancelar solicitaçãoUSER_ROLE_REQUESTS_PENDING— pendentes para aprovação
Definições de aprovação
Regras que determinam quem pode aprovar ou rejeitar uma solicitação de cargo e como o backend valida.
- Quem pode aprovar: apenas usuários cujo papel atual pertence ao conjunto de papéis aprovadores do cargo solicitado (conforme PRD: nível hierárquico superior ao cargo solicitado). Esse conjunto é obtido pelo catálogo do ministério: papéis em nível hierárquico superior ao cargo solicitado (hierarchyLevel menor) e com
can_approve_roles: trueno catálogo (BD). - Escopo: o aprovador e o solicitante devem ser do mesmo ministério (
requester.ministryId === actor.ministryId). Caso contrário o backend retorna erro de escopo. - Validação no backend (USER_ROLE_REQUEST_APPROVE / REJECT):
- Payload exige
requestIderequesterId. - Carrega o perfil do solicitante (
requesterId); se não existir ou ministério diferente →PROFILE_NOT_FOUNDouFORBIDDEN_SCOPE_MISMATCH. - Localiza a solicitação em
requester.roleRequestscom orequestIde statusPENDING; se não existir ou não estiver pendente →REQUEST_NOT_FOUND. - Obtém os papéis aprovadores para o cargo solicitado:
getApproverRoleIdsForRequestedRole(ministryId, requestedRoleId). - Se
actor.rolenão estiver emapproverRoleIds→FORBIDDEN_NOT_APPROVER. - Atualiza a solicitação para APPROVED ou REJECTED; em caso de aprovação, atualiza
requester.activeRoleerequester.roleAssignments.
- Payload exige
- Lista de pendentes (USER_ROLE_REQUESTS_PENDING): o backend retorna apenas solicitações PENDING para as quais o papel do ator está em
approverRoleIdsdo cargo solicitado (e mesmo ministério). O backend usa active_role/primary_role do perfil (BD) do ator como autoridade para decidir quais pendentes mostrar; o papel no token (actor.role) é usado só como fallback. Assim a sub-aba "Aprovações pendentes" reflete o cargo ativo gravado no perfil, e não apenas o token. - Aprovar/Rejeitar: na validação de quem pode aprovar, o backend também usa active_role/primary_role do perfil do ator (BD), com fallback para o token.
- Frontend: a sub-aba "Aprovações pendentes" e os botões Aprovar/Rejeitar são exibidos quando o cargo ativo (ou principal) do perfil tem
canApproveRoles === trueno catálogo. O backend usa o perfil (BD) para listar pendentes e para permitir aprovação.
Botões na aba Aprovações pendentes
| Botão | Regra (quem pode) | O que acontece com o pedido |
|---|---|---|
| Aprovar | Aprovador: mesmo ministério e papel em approverRoleIds do cargo solicitado (nível hierárquico superior + can_approve_roles). Payload: requestId, requesterId. | Status → APPROVED; no perfil do solicitante: active_role = cargo solicitado; novo item em role_assignments com status APPROVED. O pedido deixa de ser PENDING e sai da lista de pendentes. |
| Rejeitar | Mesma regra que Aprovar. | Status → REJECTED; reviewedAt e reviewedBy gravados. O pedido deixa de ser PENDING e sai da lista de pendentes. |
| Excluir (ícone remover) | Na UI atual, Excluir na lista de aprovações pendentes chama a mesma ação que Rejeitar (USER_ROLE_REQUEST_REJECT). Ou seja, o aprovador “exclui” da sua lista rejeitando o pedido. | Igual a Rejeitar. |
Observação: Só o solicitante pode cancelar a própria solicitação (ação USER_ROLE_REQUEST_DELETE), na aba Minhas solicitações (botão Excluir lá chama deleteRoleRequest). O aprovador não pode “apagar” o pedido sem rejeitar; ele só pode Aprovar ou Rejeitar.
Interface de Usuário (UI)
Aba Perfil na EBD do ProfileDialog.
Componentes e organização:
- Cargo ativo — ChoiceChips com papéis elegíveis; seleção única; ao mudar, pode exigir confirmação
- Cargo desejado — Dropdown com papéis disponíveis para solicitação; loading "Carregando papéis habilitados..."
- Solicitar aprovação — abre sheet para informar cargo e justificativa; envia USER_ROLE_REQUEST
Regra de preenchimento do combo Cargo desejado:
-
O backend retorna papéis elegíveis via
getAvailableRoleCatalog(unitId, requesterRole). -
unitId: igreja selecionada no dropdown (aba Dados) ou
organizationNode?.id/unitIddo perfil. -
requesterRole: cargo ativo ou papel principal do usuário.
-
Papéis exibidos são filtrados por:
- Visibilidade por igreja —
ChurchRoleVisibilityda igreja; se não configurado, usa o catálogo do ministério. - Hierarquia e teto — todos os papéis abaixo do cargo atual (
hierarchyLevelmaior) e, entre os acima (hierarchyLevelmenor), apenas os elegíveis: se o cargo atual tiverrequest_ceiling_levelno catálogo (BD), são incluídos todos os papéis comhierarchyLevel >= request_ceiling_level(teto = menor nível / maior cargo que o usuário pode solicitar); caso contrário, apenas o nível imediatamente superior. - Filtro por estado do usuário — no combo são exibidos apenas os cargos que o usuário ainda não possui e ainda não solicitou. Ficam fora do combo: (a) cargos já aprovados (presentes em
roleAssignmentscom status APPROVED, ouprimaryRole/activeRole); (b) cargos com solicitação pendente (PENDING). Assim o combo só oferece opções que fazem sentido solicitar.
- Visibilidade por igreja —
-
Sem
requesterRole, mostra todos os papéis visíveis na igreja (respeitando o filtro acima). -
Solicitações — exibidas em duas sub-abas (uma por vez):
- Minhas solicitações — lista das próprias solicitações (com opção excluir); mensagem "Nenhuma solicitação sua no momento." quando vazio.
- Aprovações pendentes — lista de solicitações que o usuário pode aprovar/rejeitar (quando tem cargo com
can_approve_roles); botões Aprovar, Rejeitar, Excluir com confirmação quando aplicável; mensagem "Nenhuma aprovação pendente." quando vazio.
-
Exibição de "Cargo atual" e status das solicitações
Observação: Usuário Visitante/Membro vê subset restrito (apenas níveis superiores imediatos). O combo fica vazio quando não há papéis elegíveis (ex.: requester já tem o nível mais alto).
Fluxo
- Carregar catálogo:
getAvailableRoleCatalog(unitId, requesterRole) - Solicitar cargo: USER_ROLE_REQUEST
- Aprovar/rejeitar: USER_ROLE_REQUEST_APPROVE / REJECT
Reflexo em tela (requisito)
Conforme ADR-0021 — Reflexo em tela após ações do usuário: ao executar qualquer comando local que altere o perfil ou realize chamadas ao backend, as mudanças devem ser refletidas imediatamente na aba Perfil na EBD, sem precisar fechar e reabrir o diálogo.
Comandos abrangidos:
| Ação | Reflexos esperados na tela |
|---|---|
| Confirmar troca de cargo (ChoiceChip ou botão "Confirma o novo cargo") | "Cargo atual de operação" atualizado; ChoiceChips com o novo cargo selecionado; combo "Cargo desejado" recalculado (papéis elegíveis dependem do requesterRole); lista de solicitações próprias e pendentes atualizada se aplicável. |
| Solicitar aprovação de cargo | Sub-aba "Minhas solicitações" atualizada (nova solicitação); combo recalculado (cargo solicitado sai da lista, pois fica pendente). |
| Aprovar / Rejeitar solicitação (pendente) | Sub-aba "Aprovações pendentes" atualizada (item removido); demais dados do perfil do aprovador refletidos conforme backend. |
| Excluir solicitação própria | Sub-aba "Minhas solicitações" atualizada (solicitação removida). |
Regra geral: Após cada ação bem-sucedida, o estado local do diálogo (_profileState, _availableRoleCatalog, _pendingApprovalRequests) deve ser atualizado a partir do backend (ou do perfil em memória do serviço) e um setState deve ser disparado para redesenhar a aba com cargo atual, combo, chips e listas consistentes.
Código de referência
frontend/lib/presentation/modules/profile_dialog/home_profile_dialog_tabs.dartfrontend/lib/application/services/role_catalog_service.dartbackend/functions/src/application/services/role-catalog-service.ts