package dev.lions.unionflow.server.entity; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * Entité Ligne Budgétaire * * Représente une ligne dans un budget (catégorie de dépense/recette). * * @author UnionFlow Team * @version 1.0 * @since 2026-03-13 */ @Entity @Table(name = "budget_lines", indexes = { @Index(name = "idx_budget_line_budget", columnList = "budget_id"), @Index(name = "idx_budget_line_category", columnList = "category") }) @Data @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) public class BudgetLine extends BaseEntity { /** Budget parent */ @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "budget_id", nullable = false) private Budget budget; /** Catégorie (CONTRIBUTIONS, SAVINGS, SOLIDARITY, EVENTS, OPERATIONAL, INVESTMENTS, OTHER) */ @NotBlank @Pattern(regexp = "^(CONTRIBUTIONS|SAVINGS|SOLIDARITY|EVENTS|OPERATIONAL|INVESTMENTS|OTHER)$") @Column(name = "category", nullable = false, length = 20) private String category; /** Nom de la ligne */ @NotBlank @Size(max = 200) @Column(name = "name", nullable = false, length = 200) private String name; /** Description optionnelle */ @Size(max = 500) @Column(name = "description", length = 500) private String description; /** Montant prévu */ @NotNull @DecimalMin(value = "0.0") @Digits(integer = 14, fraction = 2) @Column(name = "amount_planned", nullable = false, precision = 16, scale = 2) private BigDecimal amountPlanned; /** Montant réalisé */ @DecimalMin(value = "0.0") @Digits(integer = 14, fraction = 2) @Builder.Default @Column(name = "amount_realized", nullable = false, precision = 16, scale = 2) private BigDecimal amountRealized = BigDecimal.ZERO; /** Notes additionnelles */ @Size(max = 1000) @Column(name = "notes", length = 1000) private String notes; @PrePersist protected void onCreate() { super.onCreate(); if (amountRealized == null) { amountRealized = BigDecimal.ZERO; } } /** Méthode métier pour calculer le taux de réalisation (%) */ public double getRealizationRate() { if (amountPlanned.compareTo(BigDecimal.ZERO) == 0) { return 0.0; } return amountRealized.divide(amountPlanned, 4, java.math.RoundingMode.HALF_UP) .multiply(new BigDecimal("100")) .doubleValue(); } /** Méthode métier pour calculer l'écart */ public BigDecimal getVariance() { return amountRealized.subtract(amountPlanned); } /** Méthode métier pour vérifier si la ligne est dépassée */ public boolean isOverBudget() { return amountRealized.compareTo(amountPlanned) > 0; } }