sgmp/ARQUITETURA_APROVACAO.md

333 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
- **`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
- **`StatusSolicitacao`**: Enum com os status possíveis
- `RASCUNHO`, `AGUARDANDO_HEAD`, `ENVIADA`, `APROVADA_GG`, `APROVADA_CONTROLADORIA`, `APROVADA_DIRETORIA`, `AGUARDANDO_DIRETORIA`, `FINALIZADA`, `REPROVADA`
- **`EtapaAprovacao`**: Enum com as etapas
- `HEAD`, `GG`, `CONTROLADORIA`, `DIRETORIA`
- **`UsuarioSistema` e `UsuarioPerfilExtra`**:
- `UsuarioSistema.perfil` define o perfil principal.
- `UsuarioPerfilExtra` permite atribuir perfis adicionais (multi-perfis).
- Métodos como `tem_perfil` e `perfis_ativos` centralizam 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:
1. Valida se a solicitação está em `AGUARDANDO_HEAD`.
2. Valida se o usuário tem perfil de Head (`tem_perfil('HEAD')`) e está vinculado como head do gestor solicitante.
3. Cria `Aprovacao` na etapa HEAD.
4. Atualiza o status para a próxima etapa (tipicamente `ENVIADA`) ou `REPROVADA`.
- **`registrar_parecer()`** registro de parecer técnico (GG / Controladoria):
1. Valida se a solicitação está em `ENVIADA` e se o usuário pode dar parecer.
2. Cria `Parecer` para a etapa correspondente (GG ou CONTROLADORIA).
3. Quando existem pareceres das duas etapas, atualiza status para `AGUARDANDO_DIRETORIA`.
- **`aprovar_reprovar_solicitacao()`** decisão final da Diretoria:
1. Valida se a solicitação está em `AGUARDANDO_DIRETORIA`.
2. Valida se o usuário tem perfil de Diretoria.
3. Cria `Aprovacao` na etapa DIRETORIA.
4. Atualiza o status para `FINALIZADA` ou `REPROVADA`.
#### 3. Views (`views.py`)
- **`decidir_solicitacao()`**: View que recebe POST com decisão e justificativa
- Decorator: `@requer_perfil(HEAD, DIRETORIA)`
- Para status `AGUARDANDO_HEAD`, chama `aprovar_reprovar_por_head()`
- Para status `AGUARDANDO_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
---
## 🔄 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`
```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