Files
unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/TransactionApproval.java
lionsdev 6e9841b3bb
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m22s
fix(disaster-recovery 2/2): restaurer 242 fichiers Java modifiés par a72ab54
Suite à la récupération précédente (044ca4b) qui n'avait restauré que les
fichiers SUPPRIMÉS, ce commit restaure les MODIFICATIONS d'entités/services
qui étaient nécessaires pour que les fichiers restaurés compilent.

Restaurés depuis a72ab54^ (= 31330d9 + corrections) :
- Entities : Organisation, FormuleAbonnement, AuditService, MembreOrganisation, SouscriptionOrganisation, etc.
- Services : MigrerOrganisationsVersKeycloakService, ComptabilitePdfService, KycAmlService, AuditService.logKycRisqueEleve, etc.
- Resources : PaiementUnifieResource, etc.

Backend compile désormais (BUILD SUCCESS).
2026-04-25 01:05:08 +00:00

184 lines
6.3 KiB
Java

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);
}
}