11 KiB
11 KiB
Arquitetura de Aprovação - SGMP_PROD
📋 Situação Atual
Fluxo de Aprovação Atual
GESTOR cria solicitação
↓
Status: RASCUNHO
↓
GESTOR envia para aprovação
↓
Status: AGUARDANDO_HEAD (quando há etapa de Head)
↓
HEAD analisa e APROVA/REPROVA (limitado aos gestores sob sua responsabilidade)
↓
Status: ENVIADA
↓
GG registra PARECER (não aprova/reprova)
↓
CONTROLADORIA registra PARECER (não aprova/reprova)
↓
Status: AGUARDANDO_DIRETORIA
↓
DIRETORIA analisa e APROVA/REPROVA
↓
Se aprovada → Status: FINALIZADA
Se reprovada → Status: REPROVADA
Componentes Atuais
1. Modelos (models.py)
-
Solicitacao: Entidade central- Método
etapa_atual(): Retorna a etapa atual baseada no status. - Método
pode_aprovar(usuario): Verifica se o usuário pode aprovar na etapa atual (Head ou Diretoria). - Método
pode_dar_parecer(usuario): Verifica se GG/Controladoria podem registrar parecer.
- Método
-
Parecer: Registro de parecer técnico- Emitido por GG ou CONTROLADORIA.
- Não altera o status diretamente; quando existem pareceres das duas etapas, a solicitação avança para
AGUARDANDO_DIRETORIA.
-
Aprovacao: Registra cada decisão (aprovar/reprovar) em cada etapa- Campos:
solicitacao,etapa,decisao,usuario,justificativa,decidido_em - Unique constraint:
(solicitacao, etapa)- garante uma aprovação por etapa
- Campos:
-
StatusSolicitacao: Enum com os status possíveisRASCUNHO,AGUARDANDO_HEAD,ENVIADA,APROVADA_GG,APROVADA_CONTROLADORIA,APROVADA_DIRETORIA,AGUARDANDO_DIRETORIA,FINALIZADA,REPROVADA
-
EtapaAprovacao: Enum com as etapasHEAD,GG,CONTROLADORIA,DIRETORIA
-
UsuarioSistemaeUsuarioPerfilExtra:UsuarioSistema.perfildefine o perfil principal.UsuarioPerfilExtrapermite atribuir perfis adicionais (multi-perfis).- Métodos como
tem_perfileperfis_ativoscentralizam a checagem de perfis.
-
HeadGestor:- Vínculo Head → Gestores: define para quais gestores um Head pode aprovar solicitações.
2. Services (services.py)
-
aprovar_reprovar_por_head()– decisão da etapa HEAD:- Valida se a solicitação está em
AGUARDANDO_HEAD. - Valida se o usuário tem perfil de Head (
tem_perfil('HEAD')) e está vinculado como head do gestor solicitante. - Cria
Aprovacaona etapa HEAD. - Atualiza o status para a próxima etapa (tipicamente
ENVIADA) ouREPROVADA.
- Valida se a solicitação está em
-
registrar_parecer()– registro de parecer técnico (GG / Controladoria):- Valida se a solicitação está em
ENVIADAe se o usuário pode dar parecer. - Cria
Parecerpara a etapa correspondente (GG ou CONTROLADORIA). - Quando existem pareceres das duas etapas, atualiza status para
AGUARDANDO_DIRETORIA.
- Valida se a solicitação está em
-
aprovar_reprovar_solicitacao()– decisão final da Diretoria:- Valida se a solicitação está em
AGUARDANDO_DIRETORIA. - Valida se o usuário tem perfil de Diretoria.
- Cria
Aprovacaona etapa DIRETORIA. - Atualiza o status para
FINALIZADAouREPROVADA.
- Valida se a solicitação está em
3. Views (views.py)
-
decidir_solicitacao(): View que recebe POST com decisão e justificativa- Decorator:
@requer_perfil(HEAD, DIRETORIA) - Para status
AGUARDANDO_HEAD, chamaaprovar_reprovar_por_head() - Para status
AGUARDANDO_DIRETORIA, chamaaprovar_reprovar_solicitacao()
- Decorator:
-
solicitacao_detalhe(): Exibe detalhes e botões de aprovação- Calcula
pode_aprovarusandosolicitacao.pode_aprovar(usuario)
- Calcula
-
dashboard_view(): Lista solicitações com informações depode_aprovar
4. Templates
dashboard.html: Mostra botões "Aprovar" e "Reprovar" seitem.pode_aprovar == Truesolicitacao_detalhe.html: Similar, mostra botões de aprovação
🔄 Arquitetura Detalhada (pareceres + Diretoria)
Fluxo com Head, pareceres e Diretoria
GESTOR cria solicitação
↓
Status: RASCUNHO
↓
GESTOR envia para aprovação
↓
Status: AGUARDANDO_HEAD (quando aplicável; senão vai direto para ENVIADA)
↓
HEAD aprova/reprova (etapa HEAD)
↓
Status: ENVIADA
↓
GG registra PARECER (não aprova/reprova)
↓
CONTROLADORIA registra PARECER (não aprova/reprova)
↓
Status: AGUARDANDO_DIRETORIA (novo status)
↓
DIRETORIA analisa pareceres e APROVA/REPROVA
↓
Se aprovada → Status: FINALIZADA
Se reprovada → Status: REPROVADA
Mudanças Necessárias
1. Novo Modelo: Parecer
class Parecer(BaseModel):
"""
Representa um parecer técnico emitido por GG ou CONTROLADORIA
sobre uma solicitação. Diferente de Aprovacao, um Parecer não
altera o status da solicitação, apenas fornece análise e dados.
"""
solicitacao = models.ForeignKey(Solicitacao, on_delete=models.CASCADE, related_name="pareceres")
etapa = models.CharField(max_length=20, choices=EtapaAprovacao.choices) # GG ou CONTROLADORIA
usuario = models.ForeignKey(UsuarioSistema, on_delete=models.PROTECT)
texto = models.TextField(help_text="Análise, dados e considerações sobre a solicitação")
criado_em = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ("solicitacao", "etapa") # Um parecer por etapa
2. Modificar StatusSolicitacao
Adicionar novo status:
AGUARDANDO_DIRETORIA = "AGUARDANDO_DIRETORIA", _("Aguardando Diretoria")
3. Modificar Solicitacao.etapa_atual()
def etapa_atual(self):
mapa = {
StatusSolicitacao.ENVIADA: None, # GG e CONTROLADORIA podem dar parecer
StatusSolicitacao.AGUARDANDO_DIRETORIA: EtapaAprovacao.DIRETORIA,
}
return mapa.get(self.status)
4. Modificar Solicitacao.pode_aprovar()
def pode_aprovar(self, usuario=None):
"""
Apenas DIRETORIA pode aprovar/reprovar.
GG e CONTROLADORIA apenas podem dar parecer.
"""
etapa_atual = self.etapa_atual()
if etapa_atual is None:
return False
# Apenas DIRETORIA pode aprovar
if etapa_atual != EtapaAprovacao.DIRETORIA:
return False
if usuario:
if usuario.perfil != UsuarioSistema.Perfil.DIRETORIA:
return False
return True
5. Novo método: Solicitacao.pode_dar_parecer()
def pode_dar_parecer(self, usuario):
"""
Verifica se o usuário pode dar parecer na solicitação.
GG e CONTROLADORIA podem dar parecer quando status é ENVIADA.
"""
if self.status != StatusSolicitacao.ENVIADA:
return False
# Verifica se já deu parecer
parecer_existente = self.pareceres.filter(etapa=usuario.perfil).exists()
if parecer_existente:
return False # Já deu parecer
# GG e CONTROLADORIA podem dar parecer
if usuario.perfil in [UsuarioSistema.Perfil.GG, UsuarioSistema.Perfil.CONTROLADORIA]:
return True
return False
6. Novo Service: registrar_parecer()
@transaction.atomic
def registrar_parecer(
solicitacao: Solicitacao,
usuario: UsuarioSistema,
texto: str
) -> Parecer:
"""
Registra um parecer de GG ou CONTROLADORIA.
Não altera o status da solicitação.
"""
if not solicitacao.pode_dar_parecer(usuario):
raise PermissaoError("Usuário não pode dar parecer nesta solicitação.")
# Mapeia perfil para etapa
mapa_perfil_etapa = {
UsuarioSistema.Perfil.GG: EtapaAprovacao.GG,
UsuarioSistema.Perfil.CONTROLADORIA: EtapaAprovacao.CONTROLADORIA,
}
etapa = mapa_perfil_etapa.get(usuario.perfil)
parecer = Parecer.objects.create(
solicitacao=solicitacao,
etapa=etapa,
usuario=usuario,
texto=texto
)
# Verifica se ambos os pareceres foram dados
parecer_gg = Parecer.objects.filter(solicitacao=solicitacao, etapa=EtapaAprovacao.GG).exists()
parecer_controladoria = Parecer.objects.filter(solicitacao=solicitacao, etapa=EtapaAprovacao.CONTROLADORIA).exists()
if parecer_gg and parecer_controladoria:
# Ambos os pareceres foram dados, muda status para AGUARDANDO_DIRETORIA
solicitacao.status = StatusSolicitacao.AGUARDANDO_DIRETORIA
solicitacao.save()
return parecer
7. Modificar aprovar_reprovar_solicitacao()
Agora apenas DIRETORIA pode usar esta função:
@transaction.atomic
def aprovar_reprovar_solicitacao(
solicitacao: Solicitacao,
aprovador: UsuarioSistema,
decisao: str,
justificativa: str = ""
) -> Solicitacao:
"""
Apenas DIRETORIA pode aprovar/reprovar.
A solicitação deve estar em AGUARDANDO_DIRETORIA.
"""
if aprovador.perfil != UsuarioSistema.Perfil.DIRETORIA:
raise PermissaoError("Apenas a Diretoria pode aprovar/reprovar solicitações.")
if solicitacao.status != StatusSolicitacao.AGUARDANDO_DIRETORIA:
raise ValidacaoError("A solicitação não está aguardando aprovação da Diretoria.")
# ... resto da lógica similar
8. Nova View: registrar_parecer_view()
@login_required
@requer_perfil(UsuarioSistema.Perfil.GG, UsuarioSistema.Perfil.CONTROLADORIA)
def registrar_parecer_view(request, solicitacao_id):
solicitacao = get_object_or_404(Solicitacao, id=solicitacao_id)
usuario = get_usuario_sistema(request)
if request.method == "POST":
texto = request.POST.get("texto", "").strip()
if not texto:
messages.error(request, "O parecer não pode estar vazio.")
else:
try:
services.registrar_parecer(solicitacao, usuario, texto)
messages.success(request, "Parecer registrado com sucesso.")
except Exception as e:
messages.error(request, str(e))
return redirect("solicitacoes:solicitacao_detalhe", solicitacao_id=solicitacao.id)
9. Modificar Templates
- Dashboard: Mostrar botão "Dar Parecer" para GG/CONTROLADORIA quando
pode_dar_parecer == True - Detalhes: Mostrar campo de texto para parecer e botão "Registrar Parecer" para GG/CONTROLADORIA
- Mostrar pareceres já registrados na seção de auditoria
📊 Resumo das Mudanças
| Componente | Mudança |
|---|---|
| Modelo | Adicionar Parecer |
| StatusSolicitacao | Adicionar AGUARDANDO_DIRETORIA |
| Solicitacao.etapa_atual() | Retornar None para ENVIADA, DIRETORIA para AGUARDANDO_DIRETORIA |
| Solicitacao.pode_aprovar() | Apenas DIRETORIA pode aprovar |
| Solicitacao | Adicionar método pode_dar_parecer() |
| Services | Novo: registrar_parecer(), modificar aprovar_reprovar_solicitacao() |
| Views | Novo: registrar_parecer_view(), modificar decidir_solicitacao() |
| Templates | Mostrar campos de parecer para GG/CONTROLADORIA, botões de aprovação apenas para DIRETORIA |
✅ Benefícios da Nova Arquitetura
- Separação de responsabilidades: GG e CONTROLADORIA fornecem análise, DIRETORIA decide
- Flexibilidade: Pareceres podem ser editados (se necessário) sem afetar o fluxo
- Rastreabilidade: Histórico completo de pareceres e decisão final
- Clareza: Status
AGUARDANDO_DIRETORIAdeixa claro que está aguardando decisão final