- Entités : Versement, VersementObjet (lien polymorphique vers cotisation/adhesion/etc.) - VersementRepository : requêtes par membre, org, période - VersementResource : endpoints REST /api/versements - VersementService : logique métier (validation, rattachement objets) - Migration V27 : ajout numeroTelephone sur versements
172 lines
7.0 KiB
Java
172 lines
7.0 KiB
Java
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.
|
|
*
|
|
* <p>Un versement peut être effectué :
|
|
* <ul>
|
|
* <li>Via <b>Wave Mobile Money</b> : deep link natif, app Wave sur le même téléphone</li>
|
|
* <li>Manuellement : espèces, virement, chèque → statut EN_ATTENTE_VALIDATION</li>
|
|
* </ul>
|
|
*
|
|
* <p>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<VersementObjet> 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();
|
|
}
|
|
}
|
|
}
|