Files
sigrhapf/Documents/sigfip/sigefp/CORRECOES_CRITICAS_ORCAMENTO.md
2026-05-19 11:45:46 +00:00

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 - totalCommitted fica 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)

  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)

  1. Substituir IllegalArgumentException por exceções customizadas
  2. Melhorar tratamento de erros nos controllers
  3. Melhorar BudgetIntegrationService (re-throw exceções específicas)
  4. Adicionar validação de TRANSFER_OUT/CANCELLATION

Documento gerado em: 2025-01-XX
Versão: 1.0