sgmp/ARQUITETURA_APROVACAO.md

9.5 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: ENVIADA
    ↓
GG analisa e APROVA/REPROVA
    ↓
Se aprovada → Status: APROVADA_GG
    ↓
CONTROLADORIA analisa e APROVA/REPROVA
    ↓
Se aprovada → Status: APROVADA_CONTROLADORIA
    ↓
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 perfil do usuário corresponde à etapa atual
  • 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
  • StatusSolicitacao: Enum com os status possíveis

    • RASCUNHO, ENVIADA, APROVADA_GG, APROVADA_CONTROLADORIA, APROVADA_DIRETORIA, FINALIZADA, REPROVADA
  • EtapaAprovacao: Enum com as etapas

    • GG, CONTROLADORIA, DIRETORIA

2. Services (services.py)

  • aprovar_reprovar_solicitacao(): Função principal que:
    1. Valida se a solicitação está em uma etapa válida
    2. Valida se o perfil do usuário corresponde à etapa atual
    3. Cria registro de Aprovacao
    4. Atualiza o status da solicitação conforme a decisão
    5. Se reprovado, finaliza a solicitação
    6. Se aprovado, avança para o próximo status

3. Views (views.py)

  • decidir_solicitacao(): View que recebe POST com decisão e justificativa

    • Decorator: @requer_perfil(GG, CONTROLADORIA, DIRETORIA)
    • Chama aprovar_reprovar_solicitacao()
  • solicitacao_detalhe(): Exibe detalhes e botões de aprovação

    • Calcula pode_aprovar usando solicitacao.pode_aprovar(usuario)
  • dashboard_view(): Lista solicitações com informações de pode_aprovar

4. Templates

  • dashboard.html: Mostra botões "Aprovar" e "Reprovar" se item.pode_aprovar == True
  • solicitacao_detalhe.html: Similar, mostra botões de aprovação

🔄 Nova Arquitetura Proposta

Novo Fluxo

GESTOR cria solicitação
    ↓
Status: RASCUNHO
    ↓
GESTOR envia para aprovação
    ↓
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

  1. Separação de responsabilidades: GG e CONTROLADORIA fornecem análise, DIRETORIA decide
  2. Flexibilidade: Pareceres podem ser editados (se necessário) sem afetar o fluxo
  3. Rastreabilidade: Histórico completo de pareceres e decisão final
  4. Clareza: Status AGUARDANDO_DIRETORIA deixa claro que está aguardando decisão final