feat: otimização de performance e ajustes finais

This commit is contained in:
Idrissa Banora
2026-05-18 10:49:32 +00:00
commit 430deed1cd
530 changed files with 150759 additions and 0 deletions
@@ -0,0 +1,286 @@
# 🔧 Correções Críticas Necessárias - Módulo de Orçamento
**Data:** 2025-01-XX
**Prioridade:** 🔴 CRÍTICO
**Baseado em:** `ANALISE_TECNICA_PROFUNDA_ORCAMENTO.md`
---
## 🚨 Problema Crítico #1: Cálculo Incorreto de totalCommitted
### Problema Identificado
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/domain/BudgetLine.java` (Linha 49)
```java
// ❌ ERRADO - Soma TODOS os tipos de movimento
@org.hibernate.annotations.Formula("(SELECT COALESCE(SUM(bex.amount), 0) FROM budget_execution bex WHERE bex.budget_line_id = id)")
private BigDecimal totalCommitted;
```
**Impacto:**
- Soma COMMITMENT + LIQUIDATION + PAYMENT
- `availableBalance = totalAllocated - totalCommitted` fica **INCORRETO**
- Permite criar COMMITMENTs além do disponível
- **Quebra integridade orçamentária**
### Correção
```java
// ✅ CORRETO - Soma apenas COMMITMENT
@org.hibernate.annotations.Formula("(SELECT COALESCE(SUM(bex.amount), 0) FROM budget_execution bex WHERE bex.budget_line_id = id AND bex.movement_type = 'COMMITMENT')")
private BigDecimal totalCommitted;
```
**Nota:** O `BudgetExecutionRepository.calculateTotalCommittedByBudgetLineId()` já está correto (filtra por COMMITMENT), mas o @Formula não.
---
## 🚨 Problema Crítico #2: Falta Validação de Sequência
### Problema Identificado
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/service/BudgetExecutionService.java`
**Cenário Atual:**
- ✅ Valida COMMITMENT (saldo disponível)
-**NÃO valida** se existe COMMITMENT antes de criar LIQUIDATION
-**NÃO valida** se existe LIQUIDATION antes de criar PAYMENT
- ❌ Permite criar PAYMENT sem COMMITMENT/LIQUIDATION
**Impacto:**
- Quebra ciclo de execução orçamentária (COMMITMENT → LIQUIDATION → PAYMENT)
- Permite "pular" etapas
- **Não conforma com normas GFP**
### Correção Necessária
Adicionar validações no método `registerExecution()`:
```java
// Após validação de COMMITMENT (linha 59)
// Validação 3: Sequência para LIQUIDATION
if ("LIQUIDATION".equals(dto.getMovementType())) {
if (dto.getReferenceId() == null) {
throw new BusinessException("ReferenceId é obrigatório para LIQUIDATION",
"MISSING_REFERENCE_ID", HttpStatus.BAD_REQUEST);
}
// Buscar COMMITMENT correspondente
List<BudgetExecution> commitments = budgetExecutionRepository
.findByBudgetLineIdAndMovementType(budgetLine.getId(), "COMMITMENT");
// Filtrar por referenceId se fornecido
BigDecimal totalCommitted = commitments.stream()
.filter(c -> dto.getReferenceId().equals(c.getReferenceId()))
.map(BudgetExecution::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (totalCommitted.compareTo(BigDecimal.ZERO) == 0) {
throw new BusinessException("Não existe COMMITMENT correspondente para liquidar",
"NO_COMMITMENT", HttpStatus.CONFLICT);
}
// Calcular total já liquidado
List<BudgetExecution> liquidations = budgetExecutionRepository
.findByBudgetLineIdAndMovementType(budgetLine.getId(), "LIQUIDATION");
BigDecimal totalLiquidated = liquidations.stream()
.filter(l -> dto.getReferenceId().equals(l.getReferenceId()))
.map(BudgetExecution::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal availableToLiquidate = totalCommitted.subtract(totalLiquidated);
if (dto.getAmount().compareTo(availableToLiquidate) > 0) {
throw new BusinessException(
String.format("Liquidação não pode exceder empenho. Disponível: %s, Solicitado: %s",
availableToLiquidate, dto.getAmount()),
"INSUFFICIENT_COMMITMENT", HttpStatus.CONFLICT);
}
}
// Validação 4: Sequência para PAYMENT
if ("PAYMENT".equals(dto.getMovementType())) {
if (dto.getReferenceId() == null) {
throw new BusinessException("ReferenceId é obrigatório para PAYMENT",
"MISSING_REFERENCE_ID", HttpStatus.BAD_REQUEST);
}
// Buscar LIQUIDATION correspondente
List<BudgetExecution> liquidations = budgetExecutionRepository
.findByBudgetLineIdAndMovementType(budgetLine.getId(), "LIQUIDATION");
BigDecimal totalLiquidated = liquidations.stream()
.filter(l -> dto.getReferenceId().equals(l.getReferenceId()))
.map(BudgetExecution::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (totalLiquidated.compareTo(BigDecimal.ZERO) == 0) {
throw new BusinessException("Não existe LIQUIDATION correspondente para pagar",
"NO_LIQUIDATION", HttpStatus.CONFLICT);
}
// Calcular total já pago
List<BudgetExecution> payments = budgetExecutionRepository
.findByBudgetLineIdAndMovementType(budgetLine.getId(), "PAYMENT");
BigDecimal totalPaid = payments.stream()
.filter(p -> dto.getReferenceId().equals(p.getReferenceId()))
.map(BudgetExecution::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal availableToPay = totalLiquidated.subtract(totalPaid);
if (dto.getAmount().compareTo(availableToPay) > 0) {
throw new BusinessException(
String.format("Pagamento não pode exceder liquidação. Disponível: %s, Solicitado: %s",
availableToPay, dto.getAmount()),
"INSUFFICIENT_LIQUIDATION", HttpStatus.CONFLICT);
}
}
```
**Nota:** Para melhor performance, seria ideal criar métodos no repository:
- `calculateTotalCommittedByReferenceId(UUID referenceId)`
- `calculateTotalLiquidatedByReferenceId(UUID referenceId)`
- `calculateTotalPaidByReferenceId(UUID referenceId)`
---
## 🟡 Problema Médio #3: Tratamento de Exceções
### Correção BudgetEntryService
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/service/BudgetEntryService.java`
```java
// ❌ ANTES
.orElseThrow(() -> new IllegalArgumentException("Linha orçamentária não encontrada: " + dto.getBudgetLineId()));
// ✅ DEPOIS
import br.gov.sigefp.common.exception.ResourceNotFoundException;
.orElseThrow(() -> new ResourceNotFoundException("Linha orçamentária não encontrada: " + dto.getBudgetLineId()));
```
### Correção BudgetEntryController
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/api/BudgetEntryController.java`
```java
// ❌ ANTES
catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().build();
}
// ✅ DEPOIS
import br.gov.sigefp.common.exception.ResourceNotFoundException;
import br.gov.sigefp.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BudgetEntryController {
// ...
@PostMapping
public ResponseEntity<BudgetEntryDTO> create(@Valid @RequestBody CreateBudgetEntryDTO dto) {
try {
BudgetEntryDTO created = budgetEntryService.create(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
} catch (ResourceNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} catch (BusinessException e) {
return ResponseEntity.status(e.getHttpStatus()).build();
} catch (Exception e) {
log.error("Erro inesperado ao criar entrada orçamentária", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
```
---
## 🟡 Problema Médio #4: BudgetIntegrationService
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/integration/BudgetIntegrationService.java`
```java
// ❌ ANTES
catch (Exception e) {
log.error("Erro ao criar execução orçamentária (COMMITMENT): {}", e.getMessage());
throw new RuntimeException("Erro ao criar execução orçamentária: " + e.getMessage(), e);
}
// ✅ DEPOIS
catch (InsufficientBudgetException e) {
log.error("Saldo insuficiente ao criar execução orçamentária (COMMITMENT)", e);
throw e; // Re-throw exceção específica
}
catch (BusinessException e) {
log.error("Erro de negócio ao criar execução orçamentária (COMMITMENT)", e);
throw e;
}
catch (Exception e) {
log.error("Erro inesperado ao criar execução orçamentária (COMMITMENT)", e);
throw new BusinessException("Erro ao criar execução orçamentária",
"EXECUTION_ERROR", HttpStatus.INTERNAL_SERVER_ERROR);
}
```
---
## 🟡 Problema Médio #5: Validação de TRANSFER_OUT
**Arquivo:** `sigefp-budget/src/main/java/br/gov/sigefp/budget/service/BudgetEntryService.java`
**Problema:** Não valida se TRANSFER_OUT não excede saldo disponível.
**Correção:**
```java
public BudgetEntryDTO create(CreateBudgetEntryDTO dto) {
BudgetLine budgetLine = budgetLineRepository.findById(dto.getBudgetLineId())
.orElseThrow(() -> new ResourceNotFoundException(
"Linha orçamentária não encontrada: " + dto.getBudgetLineId()));
// Validação: TRANSFER_OUT não pode exceder saldo disponível
if (dto.getType() == BudgetEntryType.TRANSFER_OUT ||
dto.getType() == BudgetEntryType.CANCELLATION) {
BigDecimal totalAllocated = budgetAllocationRepository
.calculateTotalAllocatedByBudgetLineId(budgetLine.getId());
BigDecimal totalCommitted = budgetExecutionRepository
.calculateTotalCommittedByBudgetLineId(budgetLine.getId());
BigDecimal available = totalAllocated.subtract(totalCommitted);
if (dto.getAmount().compareTo(available) > 0) {
throw new BusinessException(
String.format("Transferência/Cancelamento não pode exceder saldo disponível. Disponível: %s, Solicitado: %s",
available, dto.getAmount()),
"INSUFFICIENT_BALANCE", HttpStatus.CONFLICT);
}
}
// ... resto do código
}
```
---
## 📋 Resumo das Correções
### 🔴 Críticas (Urgente)
1. ✅ Corrigir `@Formula` de `totalCommitted` para filtrar apenas COMMITMENT
2. ✅ Adicionar validação de sequência LIQUIDATION (exige COMMITMENT)
3. ✅ Adicionar validação de sequência PAYMENT (exige LIQUIDATION)
### 🟡 Médias (Importante)
4. ✅ Substituir `IllegalArgumentException` por exceções customizadas
5. ✅ Melhorar tratamento de erros nos controllers
6. ✅ Melhorar `BudgetIntegrationService` (re-throw exceções específicas)
7. ✅ Adicionar validação de TRANSFER_OUT/CANCELLATION
---
**Documento gerado em:** 2025-01-XX
**Versão:** 1.0