# 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