10 KiB
🔧 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)
// ❌ 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 - totalCommittedfica INCORRETO- Permite criar COMMITMENTs além do disponível
- Quebra integridade orçamentária
Correção
// ✅ 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():
// 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
// ❌ 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
// ❌ 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
// ❌ 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:
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)
- ✅ Corrigir
@FormuladetotalCommittedpara filtrar apenas COMMITMENT - ✅ Adicionar validação de sequência LIQUIDATION (exige COMMITMENT)
- ✅ Adicionar validação de sequência PAYMENT (exige LIQUIDATION)
🟡 Médias (Importante)
- ✅ Substituir
IllegalArgumentExceptionpor exceções customizadas - ✅ Melhorar tratamento de erros nos controllers
- ✅ Melhorar
BudgetIntegrationService(re-throw exceções específicas) - ✅ Adicionar validação de TRANSFER_OUT/CANCELLATION
Documento gerado em: 2025-01-XX
Versão: 1.0