Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m22s
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).
184 lines
6.3 KiB
Java
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);
|
|
}
|
|
}
|