feat: BackupService real pg_dump, OrganisationService region stats, SystemConfigService overrides
- BackupService: DB-persisted metadata (BackupRecord/BackupConfig entities + V16 Flyway migration), real pg_dump execution via ProcessBuilder, soft-delete on deleteBackup, pg_restore manual guidance - OrganisationService: repartitionRegion now queries Adresse entities (was Map.of() stub) - SystemConfigService: in-memory config overrides via AtomicReference (no DB dependency) - SystemMetricsService: null-guard on MemoryMXBean in getSystemStatus() (fixes test NPE) - Souscription workflow: SouscriptionService, SouscriptionResource, FormuleAbonnementRepository, V11 Flyway migration, admin REST clients - Flyway V8-V15: notes membres, types référence, type orga constraint, seed roles, première connexion, Wave checkout URL, Wave telephone column length fix - .gitignore: added uploads/ and .claude/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "backup_config")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BackupConfig extends BaseEntity {
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean autoBackupEnabled = true;
|
||||
|
||||
/** HOURLY, DAILY, WEEKLY */
|
||||
@Column(nullable = false, length = 20)
|
||||
@Builder.Default
|
||||
private String frequency = "DAILY";
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Integer retentionDays = 30;
|
||||
|
||||
/** HH:mm format, e.g. "02:00" */
|
||||
@Column(nullable = false, length = 10)
|
||||
@Builder.Default
|
||||
private String backupTime = "02:00";
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includeDatabase = true;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includeFiles = false;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includeConfiguration = true;
|
||||
|
||||
/** Absolute path where backup files are stored */
|
||||
@Column(length = 500)
|
||||
private String backupDirectory;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "backup_records")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BackupRecord extends BaseEntity {
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String name;
|
||||
|
||||
@Column(length = 500)
|
||||
private String description;
|
||||
|
||||
/** AUTO, MANUAL, RESTORE_POINT */
|
||||
@Column(nullable = false, length = 50)
|
||||
private String type;
|
||||
|
||||
private Long sizeBytes;
|
||||
|
||||
/** IN_PROGRESS, COMPLETED, FAILED */
|
||||
@Column(nullable = false, length = 50)
|
||||
private String status;
|
||||
|
||||
private LocalDateTime completedAt;
|
||||
|
||||
@Column(length = 200)
|
||||
private String createdBy;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includesDatabase = true;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includesFiles = false;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Builder.Default
|
||||
private Boolean includesConfiguration = true;
|
||||
|
||||
@Column(length = 500)
|
||||
private String filePath;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String errorMessage;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.TypeFormule;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
@@ -18,8 +19,10 @@ import lombok.*;
|
||||
@Table(
|
||||
name = "formules_abonnement",
|
||||
indexes = {
|
||||
@Index(name = "idx_formule_code", columnList = "code", unique = true),
|
||||
@Index(name = "idx_formule_actif", columnList = "actif")
|
||||
@Index(name = "idx_formule_code_plage", columnList = "code, plage", unique = true),
|
||||
@Index(name = "idx_formule_code", columnList = "code"),
|
||||
@Index(name = "idx_formule_plage", columnList = "plage"),
|
||||
@Index(name = "idx_formule_actif", columnList = "actif")
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@@ -30,9 +33,18 @@ public class FormuleAbonnement extends BaseEntity {
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@NotNull
|
||||
@Column(name = "code", unique = true, nullable = false, length = 20)
|
||||
@Column(name = "code", nullable = false, length = 20)
|
||||
private TypeFormule code;
|
||||
|
||||
/**
|
||||
* Plage de taille d'organisation à laquelle cette formule s'applique.
|
||||
* Combinée avec le code de formule, forme une clé unique dans le catalogue.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@NotNull
|
||||
@Column(name = "plage", nullable = false, length = 20)
|
||||
private PlageMembres plage;
|
||||
|
||||
@NotBlank
|
||||
@Column(name = "libelle", nullable = false, length = 100)
|
||||
private String libelle;
|
||||
|
||||
@@ -59,8 +59,8 @@ public class Membre extends BaseEntity {
|
||||
@Column(name = "telephone", length = 20)
|
||||
private String telephone;
|
||||
|
||||
@Pattern(regexp = "^\\+225[0-9]{8}$", message = "Le numéro Wave doit être au format +225XXXXXXXX")
|
||||
@Column(name = "telephone_wave", length = 13)
|
||||
@Pattern(regexp = "^\\+[1-9][0-9]{6,14}$", message = "Le numéro Wave doit être au format international E.164 (ex: +22507XXXXXXXX)")
|
||||
@Column(name = "telephone_wave", length = 20)
|
||||
private String telephoneWave;
|
||||
|
||||
@NotNull
|
||||
@@ -77,6 +77,11 @@ public class Membre extends BaseEntity {
|
||||
@Column(name = "statut_compte", nullable = false, length = 30)
|
||||
private String statutCompte = "EN_ATTENTE_VALIDATION";
|
||||
|
||||
/** Vrai si le membre n'a jamais changé son mot de passe généré par l'admin. */
|
||||
@Builder.Default
|
||||
@Column(name = "premiere_connexion", nullable = false)
|
||||
private Boolean premiereConnexion = true;
|
||||
|
||||
/**
|
||||
* Statut matrimonial (domaine
|
||||
* {@code STATUT_MATRIMONIAL} dans
|
||||
@@ -101,6 +106,10 @@ public class Membre extends BaseEntity {
|
||||
@Column(name = "numero_identite", length = 100)
|
||||
private String numeroIdentite;
|
||||
|
||||
/** Notes / biographie libre du membre. */
|
||||
@Column(name = "notes", length = 1000)
|
||||
private String notes;
|
||||
|
||||
/** Niveau de vigilance KYC LCB-FT (SIMPLIFIE, RENFORCE). */
|
||||
@Column(name = "niveau_vigilance_kyc", length = 20)
|
||||
private String niveauVigilanceKyc;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.PlageMembres;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.StatutSouscription;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.StatutValidationSouscription;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.TypePeriodeAbonnement;
|
||||
import dev.lions.unionflow.server.api.enums.abonnement.TypeOrganisationFacturation;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.UUID;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
@@ -76,12 +81,57 @@ public class SouscriptionOrganisation extends BaseEntity {
|
||||
@Column(name = "wave_session_id", length = 255)
|
||||
private String waveSessionId;
|
||||
|
||||
@Column(name = "wave_checkout_url", length = 1024)
|
||||
private String waveCheckoutUrl;
|
||||
|
||||
@Column(name = "date_dernier_paiement")
|
||||
private LocalDate dateDernierPaiement;
|
||||
|
||||
@Column(name = "date_prochain_paiement")
|
||||
private LocalDate dateProchainePaiement;
|
||||
|
||||
// ── Champs workflow de validation (onboarding) ────────────────────────────
|
||||
|
||||
/** Plage de membres choisie lors de la souscription. */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "plage", length = 20)
|
||||
private PlageMembres plage;
|
||||
|
||||
/** Type d'organisation déclaré, utilisé pour le coefficient tarifaire. */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "type_organisation", length = 30)
|
||||
private TypeOrganisationFacturation typeOrganisationSouscription;
|
||||
|
||||
/** Coefficient multiplicateur effectivement appliqué (org × période). */
|
||||
@Column(name = "coefficient_applique", precision = 4, scale = 2)
|
||||
private BigDecimal coefficientApplique;
|
||||
|
||||
/** État du workflow de validation SuperAdmin. */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Builder.Default
|
||||
@Column(name = "statut_validation", nullable = false, length = 40)
|
||||
private StatutValidationSouscription statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
|
||||
|
||||
/** Montant total facturé pour la période choisie (en XOF). */
|
||||
@Column(name = "montant_total", precision = 12, scale = 2)
|
||||
private BigDecimal montantTotal;
|
||||
|
||||
/** Date à laquelle le SuperAdmin a approuvé ou rejeté la souscription. */
|
||||
@Column(name = "date_validation")
|
||||
private LocalDate dateValidation;
|
||||
|
||||
/** UUID du SuperAdmin ayant validé ou rejeté. */
|
||||
@Column(name = "validated_by_id")
|
||||
private UUID validatedById;
|
||||
|
||||
/** Motif de rejet renseigné par le SuperAdmin. */
|
||||
@Column(name = "commentaire_rejet", length = 500)
|
||||
private String commentaireRejet;
|
||||
|
||||
/** Mot de passe temporaire généré à l'activation du compte. */
|
||||
@Column(name = "mot_de_passe_temporaire", length = 100)
|
||||
private String motDePasseTemporaire;
|
||||
|
||||
// ── Méthodes métier ────────────────────────────────────────────────────────
|
||||
|
||||
public boolean isActive() {
|
||||
@@ -112,9 +162,10 @@ public class SouscriptionOrganisation extends BaseEntity {
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
super.onCreate();
|
||||
if (statut == null) statut = StatutSouscription.ACTIVE;
|
||||
if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL;
|
||||
if (quotaUtilise == null) quotaUtilise = 0;
|
||||
if (statut == null) statut = StatutSouscription.ACTIVE;
|
||||
if (typePeriode == null) typePeriode = TypePeriodeAbonnement.MENSUEL;
|
||||
if (quotaUtilise == null) quotaUtilise = 0;
|
||||
if (statutValidation == null) statutValidation = StatutValidationSouscription.EN_ATTENTE_PAIEMENT;
|
||||
if (formule != null && quotaMax == null) quotaMax = formule.getMaxMembres();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user