Sync: code local unifié
Synchronisation du code source local (fait foi). Signed-off-by: lions dev Team
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
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.UUID;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Entité Approbation de Transaction
|
||||
*
|
||||
* Représente une approbation dans le workflow financier multi-niveaux.
|
||||
* Chaque transaction financière au-dessus d'un certain seuil nécessite une ou plusieurs approbations.
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-13
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "transaction_approvals", indexes = {
|
||||
@Index(name = "idx_approval_transaction", columnList = "transaction_id"),
|
||||
@Index(name = "idx_approval_status", columnList = "status"),
|
||||
@Index(name = "idx_approval_requester", columnList = "requester_id"),
|
||||
@Index(name = "idx_approval_organisation", columnList = "organisation_id"),
|
||||
@Index(name = "idx_approval_created", columnList = "created_at"),
|
||||
@Index(name = "idx_approval_level", columnList = "required_level")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TransactionApproval extends BaseEntity {
|
||||
|
||||
/** ID de la transaction financière à approuver */
|
||||
@NotNull
|
||||
@Column(name = "transaction_id", nullable = false)
|
||||
private UUID transactionId;
|
||||
|
||||
/** Type de transaction (CONTRIBUTION, DEPOSIT, WITHDRAWAL, TRANSFER, SOLIDARITY, EVENT, OTHER) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(CONTRIBUTION|DEPOSIT|WITHDRAWAL|TRANSFER|SOLIDARITY|EVENT|OTHER)$")
|
||||
@Column(name = "transaction_type", nullable = false, length = 20)
|
||||
private String transactionType;
|
||||
|
||||
/** Montant de la transaction */
|
||||
@NotNull
|
||||
@DecimalMin(value = "0.0", message = "Le montant doit être positif")
|
||||
@Digits(integer = 12, fraction = 2)
|
||||
@Column(name = "amount", nullable = false, precision = 14, scale = 2)
|
||||
private BigDecimal amount;
|
||||
|
||||
/** 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 membre demandeur */
|
||||
@NotNull
|
||||
@Column(name = "requester_id", nullable = false)
|
||||
private UUID requesterId;
|
||||
|
||||
/** Nom complet du demandeur (cache pour performance) */
|
||||
@NotBlank
|
||||
@Column(name = "requester_name", nullable = false, length = 200)
|
||||
private String requesterName;
|
||||
|
||||
/** Organisation concernée (peut être null pour transactions globales) */
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "organisation_id")
|
||||
private Organisation organisation;
|
||||
|
||||
/** Niveau d'approbation requis (NONE, LEVEL1, LEVEL2, LEVEL3) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(NONE|LEVEL1|LEVEL2|LEVEL3)$")
|
||||
@Column(name = "required_level", nullable = false, length = 10)
|
||||
private String requiredLevel;
|
||||
|
||||
/** Statut de l'approbation (PENDING, APPROVED, VALIDATED, REJECTED, EXPIRED, CANCELLED) */
|
||||
@NotBlank
|
||||
@Pattern(regexp = "^(PENDING|APPROVED|VALIDATED|REJECTED|EXPIRED|CANCELLED)$")
|
||||
@Builder.Default
|
||||
@Column(name = "status", nullable = false, length = 20)
|
||||
private String status = "PENDING";
|
||||
|
||||
/** Liste des actions d'approbateurs */
|
||||
@OneToMany(mappedBy = "approval", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
@Builder.Default
|
||||
private List<ApproverAction> approvers = new ArrayList<>();
|
||||
|
||||
/** Raison du rejet (si status = REJECTED) */
|
||||
@Size(max = 1000)
|
||||
@Column(name = "rejection_reason", length = 1000)
|
||||
private String rejectionReason;
|
||||
|
||||
/** Date de création de la demande d'approbation */
|
||||
@NotNull
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/** Date d'expiration (timeout) */
|
||||
@Column(name = "expires_at")
|
||||
private LocalDateTime expiresAt;
|
||||
|
||||
/** Date de completion (approbation finale ou rejet) */
|
||||
@Column(name = "completed_at")
|
||||
private LocalDateTime completedAt;
|
||||
|
||||
/** Métadonnées additionnelles (JSON) */
|
||||
@Column(name = "metadata", columnDefinition = "TEXT")
|
||||
private String metadata;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (createdAt == null) {
|
||||
createdAt = LocalDateTime.now();
|
||||
}
|
||||
if (currency == null) {
|
||||
currency = "XOF";
|
||||
}
|
||||
if (status == null) {
|
||||
status = "PENDING";
|
||||
}
|
||||
// Expiration par défaut: 7 jours
|
||||
if (expiresAt == null) {
|
||||
expiresAt = createdAt.plusDays(7);
|
||||
}
|
||||
}
|
||||
|
||||
/** Méthode métier pour ajouter une action d'approbateur */
|
||||
public void addApproverAction(ApproverAction action) {
|
||||
approvers.add(action);
|
||||
action.setApproval(this);
|
||||
}
|
||||
|
||||
/** Méthode métier pour compter les approbations */
|
||||
public long countApprovals() {
|
||||
return approvers.stream()
|
||||
.filter(a -> "APPROVED".equals(a.getDecision()))
|
||||
.count();
|
||||
}
|
||||
|
||||
/** Méthode métier pour obtenir le nombre d'approbations requises */
|
||||
public int getRequiredApprovals() {
|
||||
return switch (requiredLevel) {
|
||||
case "NONE" -> 0;
|
||||
case "LEVEL1" -> 1;
|
||||
case "LEVEL2" -> 2;
|
||||
case "LEVEL3" -> 3;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si toutes les approbations sont reçues */
|
||||
public boolean hasAllApprovals() {
|
||||
return countApprovals() >= getRequiredApprovals();
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'approbation est expirée */
|
||||
public boolean isExpired() {
|
||||
return expiresAt != null && LocalDateTime.now().isAfter(expiresAt);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'approbation est en attente */
|
||||
public boolean isPending() {
|
||||
return "PENDING".equals(status);
|
||||
}
|
||||
|
||||
/** Méthode métier pour vérifier si l'approbation est complétée */
|
||||
public boolean isCompleted() {
|
||||
return "VALIDATED".equals(status) || "REJECTED".equals(status) || "CANCELLED".equals(status);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user