package dev.lions.unionflow.server.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import jakarta.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * Entité EcritureComptable pour les écritures comptables * * @author UnionFlow Team * @version 3.0 * @since 2025-01-29 */ @Entity @Table( name = "ecritures_comptables", indexes = { @Index(name = "idx_ecriture_numero_piece", columnList = "numero_piece", unique = true), @Index(name = "idx_ecriture_date", columnList = "date_ecriture"), @Index(name = "idx_ecriture_journal", columnList = "journal_id"), @Index(name = "idx_ecriture_organisation", columnList = "organisation_id"), @Index(name = "idx_ecriture_paiement", columnList = "paiement_id") }) @Data @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) public class EcritureComptable extends BaseEntity { /** Numéro de pièce unique */ @NotBlank @Column(name = "numero_piece", unique = true, nullable = false, length = 50) private String numeroPiece; /** Date de l'écriture */ @NotNull @Column(name = "date_ecriture", nullable = false) private LocalDate dateEcriture; /** Libellé de l'écriture */ @NotBlank @Column(name = "libelle", nullable = false, length = 500) private String libelle; /** Référence externe */ @Column(name = "reference", length = 100) private String reference; /** Lettrage (pour rapprochement) */ @Column(name = "lettrage", length = 20) private String lettrage; /** Pointage (pour rapprochement bancaire) */ @Builder.Default @Column(name = "pointe", nullable = false) private Boolean pointe = false; /** Montant total débit (somme des lignes) */ @Builder.Default @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) @Column(name = "montant_debit", precision = 14, scale = 2) private BigDecimal montantDebit = BigDecimal.ZERO; /** Montant total crédit (somme des lignes) */ @Builder.Default @DecimalMin(value = "0.0") @Digits(integer = 12, fraction = 2) @Column(name = "montant_credit", precision = 14, scale = 2) private BigDecimal montantCredit = BigDecimal.ZERO; /** Commentaires */ @Column(name = "commentaire", length = 1000) private String commentaire; // Relations @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "journal_id", nullable = false) private JournalComptable journal; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "organisation_id") private Organisation organisation; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "paiement_id") private Paiement paiement; /** Lignes d'écriture */ @JsonIgnore @OneToMany(mappedBy = "ecriture", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @Builder.Default private List lignes = new ArrayList<>(); /** Méthode métier pour vérifier l'équilibre (Débit = Crédit) */ public boolean isEquilibree() { if (montantDebit == null || montantCredit == null) { return false; } return montantDebit.compareTo(montantCredit) == 0; } /** Méthode métier pour calculer les totaux à partir des lignes */ public void calculerTotaux() { if (lignes == null || lignes.isEmpty()) { montantDebit = BigDecimal.ZERO; montantCredit = BigDecimal.ZERO; return; } montantDebit = lignes.stream() .map(LigneEcriture::getMontantDebit) .filter(amount -> amount != null) .reduce(BigDecimal.ZERO, BigDecimal::add); montantCredit = lignes.stream() .map(LigneEcriture::getMontantCredit) .filter(amount -> amount != null) .reduce(BigDecimal.ZERO, BigDecimal::add); } /** Méthode métier pour générer un numéro de pièce unique */ public static String genererNumeroPiece(String prefixe, LocalDate date) { return String.format( "%s-%04d%02d%02d-%012d", prefixe, date.getYear(), date.getMonthValue(), date.getDayOfMonth(), System.currentTimeMillis() % 1000000000000L); } /** Callback JPA avant la persistance */ @PrePersist protected void onCreate() { super.onCreate(); if (numeroPiece == null || numeroPiece.isEmpty()) { numeroPiece = genererNumeroPiece("ECR", dateEcriture != null ? dateEcriture : LocalDate.now()); } if (dateEcriture == null) { dateEcriture = LocalDate.now(); } if (montantDebit == null) { montantDebit = BigDecimal.ZERO; } if (montantCredit == null) { montantCredit = BigDecimal.ZERO; } if (pointe == null) { pointe = false; } // Calculer les totaux si les lignes sont déjà présentes if (lignes != null && !lignes.isEmpty()) { calculerTotaux(); } } /** Callback JPA avant la mise à jour */ @PreUpdate protected void onUpdate() { calculerTotaux(); } }