sgmp/ARQUITETURA_APROVACAO.md

304 lines
9.5 KiB
Markdown

# 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