feat: otimização de performance e ajustes finais
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user