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.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import lombok.*; /** * Versement — acte de régler une cotisation ou de déposer des fonds. * *

Un versement peut être effectué : *

* *

Table DB : {@code paiements} (nom hérité, conservé pour compatibilité Flyway). * * @author UnionFlow Team * @version 4.0 * @since 2026-04-13 */ @Entity @Table(name = "paiements", indexes = { @Index(name = "idx_paiement_numero_reference", columnList = "numero_reference", unique = true), @Index(name = "idx_paiement_membre", columnList = "membre_id"), @Index(name = "idx_paiement_statut", columnList = "statut_paiement"), @Index(name = "idx_paiement_methode", columnList = "methode_paiement"), @Index(name = "idx_paiement_date", columnList = "date_paiement") }) @Data @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) public class Versement extends BaseEntity { private static final AtomicLong REFERENCE_COUNTER = new AtomicLong(System.currentTimeMillis() % 1_000_000_000_000L); // ── Identité ────────────────────────────────────────────────────────────── /** Référence unique (ex: VRS-2026-XXXXXXXXXXXX) */ @NotBlank @Column(name = "numero_reference", unique = true, nullable = false, length = 50) private String numeroReference; // ── Montant ─────────────────────────────────────────────────────────────── @NotNull @DecimalMin(value = "0.0", message = "Le montant doit être positif") @Digits(integer = 12, fraction = 2) @Column(name = "montant", nullable = false, precision = 14, scale = 2) private BigDecimal montant; @NotBlank @Pattern(regexp = "^[A-Z]{3}$", message = "Code devise ISO à 3 lettres requis") @Column(name = "code_devise", nullable = false, length = 3) private String codeDevise; // ── Méthode & Statut ────────────────────────────────────────────────────── /** WAVE | ESPECES | VIREMENT | CHEQUE | AUTRE */ @NotNull @Column(name = "methode_paiement", nullable = false, length = 50) private String methodePaiement; /** EN_ATTENTE | EN_COURS | CONFIRME | ECHEC | EN_ATTENTE_VALIDATION | ANNULE */ @NotNull @Builder.Default @Column(name = "statut_paiement", nullable = false, length = 30) private String statutPaiement = "EN_ATTENTE"; // ── Dates ───────────────────────────────────────────────────────────────── @Column(name = "date_paiement") private LocalDateTime datePaiement; @Column(name = "date_validation") private LocalDateTime dateValidation; // ── Validation ──────────────────────────────────────────────────────────── @Column(name = "validateur", length = 255) private String validateur; // ── Traçabilité ─────────────────────────────────────────────────────────── /** ID transaction Wave (TCN...) ou référence chèque / bordereau */ @Column(name = "reference_externe", length = 500) private String referenceExterne; @Column(name = "url_preuve", length = 1000) private String urlPreuve; @Column(name = "commentaire", length = 1000) private String commentaire; @Column(name = "ip_address", length = 45) private String ipAddress; @Column(name = "user_agent", length = 500) private String userAgent; // ── Téléphone Wave ──────────────────────────────────────────────────────── /** * Numéro de téléphone Wave utilisé pour ce versement. * Pré-rempli depuis le profil du membre (même téléphone qu'UnionFlow), * modifiable à l'étape "Récapitulatif" avant de tapper "Payer". */ @Column(name = "numero_telephone", length = 20) private String numeroTelephone; // ── Relations ───────────────────────────────────────────────────────────── @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "membre_id", nullable = false) private Membre membre; @JsonIgnore @OneToMany(mappedBy = "versement", cascade = CascadeType.ALL, fetch = FetchType.LAZY) @Builder.Default private List versementsObjets = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "transaction_wave_id") private TransactionWave transactionWave; // ── Méthodes métier ─────────────────────────────────────────────────────── /** Génère une référence unique : VRS-YYYY-XXXXXXXXXXXX */ public static String genererNumeroReference() { return "VRS-" + LocalDateTime.now().getYear() + "-" + String.format("%012d", REFERENCE_COUNTER.incrementAndGet() % 1_000_000_000_000L); } /** Vrai si le versement est confirmé (Wave) ou validé (manuel) */ public boolean isConfirme() { return "CONFIRME".equals(statutPaiement) || "VALIDE".equals(statutPaiement); } /** Vrai si le versement peut encore être modifié ou annulé */ public boolean peutEtreModifie() { return !"CONFIRME".equals(statutPaiement) && !"VALIDE".equals(statutPaiement) && !"ANNULE".equals(statutPaiement); } @PrePersist protected void onCreate() { super.onCreate(); if (numeroReference == null || numeroReference.isBlank()) { numeroReference = genererNumeroReference(); } if (statutPaiement == null) { statutPaiement = "EN_ATTENTE"; } if (datePaiement == null) { datePaiement = LocalDateTime.now(); } } }