Sync: code local unifié
Synchronisation du code source local (fait foi). Signed-off-by: lions dev Team
This commit is contained in:
218
src/main/java/dev/lions/unionflow/server/entity/Budget.java
Normal file
218
src/main/java/dev/lions/unionflow/server/entity/Budget.java
Normal file
@@ -0,0 +1,218 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Budget
|
||||
*
|
||||
* Représente un budget prévisionnel (mensuel/trimestriel/annuel) avec suivi de réalisation.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-13
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "budgets", indexes = {
|
||||
@Index(name = "idx_budget_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_budget_status", columnList = "status"),
|
||||
@Index(name = "idx_budget_period", columnList = "period"),
|
||||
@Index(name = "idx_budget_year_month", columnList = "year, month"),
|
||||
@Index(name = "idx_budget_created_by", columnList = "created_by_id")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Budget extends BaseEntity {
|
||||
|
||||
/** Nom du budget */
|
||||
@NotBlank
|
||||
@Size(max = 200)
|
||||
@Column(name = "name", nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
/** Description optionnelle */
|
||||
@Size(max = 1000)
|
||||
@Column(name = "description", length = 1000)
|
||||
private String description;
|
||||
|
||||
/** Organisation concernée */
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id", nullable = false)
|
||||
private Organisation organisation;
|
||||
|
||||
/** Période (MONTHLY, QUARTERLY, SEMIANNUAL, ANNUAL) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(MONTHLY|QUARTERLY|SEMIANNUAL|ANNUAL)$")
|
||||
@Column(name = "period", nullable = false, length = 20)
|
||||
private String period;
|
||||
|
||||
/** Année du budget */
|
||||
@NotNull
|
||||
@Min(value = 2020, message = "L'année doit être >= 2020")
|
||||
@Max(value = 2100, message = "L'année doit être <= 2100")
|
||||
@Column(name = "year", nullable = false)
|
||||
private Integer year;
|
||||
|
||||
/** Mois (1-12) pour budget mensuel, null sinon */
|
||||
@Min(value = 1)
|
||||
@Max(value = 12)
|
||||
@Column(name = "month")
|
||||
private Integer month;
|
||||
|
||||
/** Statut (DRAFT, ACTIVE, CLOSED, CANCELLED) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(DRAFT|ACTIVE|CLOSED|CANCELLED)$")
|
||||
@Builder.Default
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
private String status = "DRAFT";
|
||||
|
||||
/** Lignes budgétaires */
|
||||
@OneToMany(mappedBy = "budget", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<BudgetLine> lines = new ArrayList<>();
|
||||
|
||||
/** Total prévu (somme des montants prévus des lignes) */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 14, fraction = 2)
|
||||
@Builder.Default
|
||||
@Column(name = "total_planned", nullable = false, precision = 16, scale = 2)
|
||||
private BigDecimal totalPlanned = BigDecimal.ZERO;
|
||||
|
||||
/** Total réalisé (somme des montants réalisés des lignes) */
|
||||
@DecimalMin(value = "0.0")
|
||||
@Digits(integer = 14, fraction = 2)
|
||||
@Builder.Default
|
||||
@Column(name = "total_realized", nullable = false, precision = 16, scale = 2)
|
||||
private BigDecimal totalRealized = BigDecimal.ZERO;
|
||||
|
||||
/** Code devise ISO 3 lettres */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^[A-Z]{3}$")
|
||||
@Builder.Default
|
||||
@Column(name = "currency", nullable = false, length = 3)
|
||||
private String currency = "XOF";
|
||||
|
||||
/** ID du créateur du budget */
|
||||
@NotNull
|
||||
@Column(name = "created_by_id", nullable = false)
|
||||
private UUID createdById;
|
||||
|
||||
/** Date de création */
|
||||
@NotNull
|
||||
@Column(name = "created_at_budget", nullable = false)
|
||||
private LocalDateTime createdAtBudget;
|
||||
|
||||
/** Date d'approbation */
|
||||
@Column(name = "approved_at")
|
||||
private LocalDateTime approvedAt;
|
||||
|
||||
/** ID de l'approbateur */
|
||||
@Column(name = "approved_by_id")
|
||||
private UUID approvedById;
|
||||
|
||||
/** Date de début de la période budgétaire */
|
||||
@NotNull
|
||||
@Column(name = "start_date", nullable = false)
|
||||
private LocalDate startDate;
|
||||
|
||||
/** Date de fin de la période budgétaire */
|
||||
@NotNull
|
||||
@Column(name = "end_date", nullable = false)
|
||||
private LocalDate endDate;
|
||||
|
||||
/** Métadonnées additionnelles (JSON) */
|
||||
@Column(name = "metadata", columnDefinition = "TEXT")
|
||||
private String metadata;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (createdAtBudget == null) {
|
||||
createdAtBudget = LocalDateTime.now();
|
||||
}
|
||||
if (currency == null) {
|
||||
currency = "XOF";
|
||||
}
|
||||
if (status == null) {
|
||||
status = "DRAFT";
|
||||
}
|
||||
if (totalPlanned == null) {
|
||||
totalPlanned = BigDecimal.ZERO;
|
||||
}
|
||||
if (totalRealized == null) {
|
||||
totalRealized = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
/** Méthode métier pour ajouter une ligne budgétaire */
|
||||
public void addLine(BudgetLine line) {
|
||||
lines.add(line);
|
||||
line.setBudget(this);
|
||||
recalculateTotals();
|
||||
}
|
||||
|
||||
/** Méthode métier pour supprimer une ligne budgétaire */
|
||||
public void removeLine(BudgetLine line) {
|
||||
lines.remove(line);
|
||||
line.setBudget(null);
|
||||
recalculateTotals();
|
||||
}
|
||||
|
||||
/** Méthode métier pour recalculer les totaux */
|
||||
public void recalculateTotals() {
|
||||
this.totalPlanned = lines.stream()
|
||||
.map(BudgetLine::getAmountPlanned)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
this.totalRealized = lines.stream()
|
||||
.map(BudgetLine::getAmountRealized)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer le taux de réalisation (%) */
|
||||
public double getRealizationRate() {
|
||||
if (totalPlanned.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return totalRealized.divide(totalPlanned, 4, java.math.RoundingMode.HALF_UP)
|
||||
.multiply(new BigDecimal("100"))
|
||||
.doubleValue();
|
||||
}
|
||||
|
||||
/** Méthode métier pour calculer l'écart (réalisé - prévu) */
|
||||
public BigDecimal getVariance() {
|
||||
return totalRealized.subtract(totalPlanned);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le budget est dépassé */
|
||||
public boolean isOverBudget() {
|
||||
return totalRealized.compareTo(totalPlanned) > 0;
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si le budget est actif */
|
||||
public boolean isActive() {
|
||||
return "ACTIVE".equals(status);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si la période est en cours */
|
||||
public boolean isCurrentPeriod() {
|
||||
LocalDate now = LocalDate.now();
|
||||
return !now.isBefore(startDate) && !now.isAfter(endDate);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user