# 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` ```python 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()` ```python 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()` ```python 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()` ```python 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()` ```python @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: ```python @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()` ```python @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