fix(frontend): corrections workflow v3.0 — inscription événements, CreateMembreRequest, AJAX session expiry

Services:
- EvenementService: POST /inscriptions (sans membreId), DELETE /inscriptions, GET /recherche, GET /type/{type}
- MembreService: creer() accepte CreateMembreRequest au lieu de MembreResponse
- Nouveaux services: BackupService, EpargneService, FinanceApprovalService, LogsService, MessageService, OrganisationService, PaiementClientService

Beans:
- MembreInscriptionBean: construit CreateMembreRequest.builder() avec organisationId UUID
- EvenementsBean: inscrireParticipant(id) sans userId (backend infère depuis token)
- DashboardBean: checkAccessAndRedirect() SUPER_ADMIN en premier

Sécurité:
- AuthenticationFilter: gestion AJAX PrimeFaces (partial/ajax → XML partial-response redirect)
- PermissionChecker: vérification rôles côté bean
- k8s/: manifestes secrets SMTP et Wave (placeholders à remplir)

Pages XHTML: dashboards rôles, cotisations, membres, événements, organisations
This commit is contained in:
dahoud
2026-04-07 20:54:20 +00:00
parent 0dc050f422
commit ac0c5a67a1
96 changed files with 7264 additions and 2497 deletions

View File

@@ -1,13 +1,14 @@
package dev.lions.unionflow.client.view;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationResponse;
import dev.lions.unionflow.server.api.dto.organisation.response.OrganisationSummaryResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.abonnement.response.AbonnementResponse;
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
import dev.lions.unionflow.server.api.dto.souscription.SouscriptionStatutResponse;
import dev.lions.unionflow.client.service.AdminUserService;
import dev.lions.unionflow.client.service.AssociationService;
import dev.lions.unionflow.client.service.OrganisationService;
import dev.lions.unionflow.client.service.AuditService;
import dev.lions.unionflow.client.service.CotisationService;
import dev.lions.unionflow.client.service.MembreService;
import dev.lions.unionflow.client.service.MetricsService;
import dev.lions.unionflow.client.service.SouscriptionService;
import io.quarkus.security.identity.SecurityIdentity;
@@ -23,6 +24,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.jboss.logging.Logger;
@@ -41,10 +43,12 @@ public class SuperAdminBean implements Serializable {
private static final String OUTCOME_SUPER_ADMIN_CONFIGURATION = "superAdminConfigurationPage";
private static final String OUTCOME_SUPER_ADMIN_ALERTES = "superAdminAlertesPage";
private static final String OUTCOME_SUPER_ADMIN_ACTIVITE = "superAdminActivitePage";
private static final String OUTCOME_SUPER_ADMIN_AUDIT = "/pages/admin/audit/journal";
private static final String OUTCOME_SUPER_ADMIN_BACKUP = "/pages/secure/admin/sauvegarde";
@Inject
@RestClient
private AssociationService associationService;
private OrganisationService organisationService;
@Inject
@RestClient
@@ -62,6 +66,10 @@ public class SuperAdminBean implements Serializable {
@RestClient
private SouscriptionService souscriptionService;
@Inject
@RestClient
private MembreService membreService;
@Inject
private MetricsService metricsService;
@@ -116,6 +124,10 @@ public class SuperAdminBean implements Serializable {
private RevenusData revenus;
private String periodeEvolution = "12M";
// Caches transients des stats (non sérialisés — rechargés à chaque nouvelle session)
private transient OrganisationService.StatistiquesOrganisationDTO orgStats;
private transient MembreService.StatistiquesMembreDTO membreStats;
@PostConstruct
public void init() {
initializeUserInfo();
@@ -142,6 +154,8 @@ public class SuperAdminBean implements Serializable {
}
private void initializeKPIs() {
loadStatsOrganisations();
loadStatsMembres();
initializeAssociationKPIs();
initializeAdminCount();
initializeCotisationKPIs();
@@ -151,22 +165,41 @@ public class SuperAdminBean implements Serializable {
calculerPourcentagesJauges();
}
private void initializeAssociationKPIs() {
private void loadStatsOrganisations() {
try {
PagedResponse<OrganisationResponse> response = associationService.listerToutes(0, 1000);
List<OrganisationResponse> associations = (response != null && response.getData() != null) ? response.getData()
: new ArrayList<>();
totalEntites = associations.size();
totalMembres = associations.stream()
.mapToInt(a -> a.getNombreMembres() != null ? a.getNombreMembres() : 0)
.sum();
croissanceEntites = "0";
nouvellesEntites = 0;
croissanceMembres = "0";
orgStats = organisationService.obtenirStatistiques();
} catch (Exception e) {
LOG.debugf("Impossible de charger les KPIs associations: %s", e.getMessage());
LOG.debugf("Impossible de charger les stats organisations: %s", e.getMessage());
orgStats = null;
}
}
private void loadStatsMembres() {
try {
membreStats = membreService.obtenirStatistiques();
} catch (Exception e) {
LOG.debugf("Impossible de charger les stats membres: %s", e.getMessage());
membreStats = null;
}
}
private void initializeAssociationKPIs() {
if (orgStats != null) {
totalEntites = orgStats.totalAssociations != null ? orgStats.totalAssociations.intValue() : 0;
nouvellesEntites = orgStats.nouvellesAssociations30Jours != null
? orgStats.nouvellesAssociations30Jours.intValue() : 0;
} else {
totalEntites = 0;
nouvellesEntites = 0;
}
croissanceEntites = nouvellesEntites > 0 ? "+" + nouvellesEntites : "0";
if (membreStats != null) {
totalMembres = membreStats.membresActifs != null ? membreStats.membresActifs.intValue() : 0;
croissanceMembres = membreStats.nouveauxMembres30Jours != null
? membreStats.nouveauxMembres30Jours.toString() : "0";
} else {
totalMembres = 0;
croissanceMembres = "0";
}
}
@@ -189,14 +222,20 @@ public class SuperAdminBean implements Serializable {
try {
Map<String, Object> stats = cotisationService.obtenirStatistiques();
if (stats != null) {
Object montantTotal = stats.get("montantTotal");
Object montantTotal = stats.get("montantTotalPaye");
if (montantTotal instanceof Number) {
revenusGlobaux = String.format("%,.0f FCFA", ((Number) montantTotal).doubleValue());
} else {
revenusGlobaux = "0 FCFA";
}
Object croissance = stats.get("croissance");
croissanceRevenus = croissance != null ? String.valueOf(croissance) : "0";
// tauxPaiement = % de cotisations payées (proxy de la santé financière)
Object croissance = stats.get("tauxPaiement");
if (croissance instanceof Number) {
double val = ((Number) croissance).doubleValue();
croissanceRevenus = val == 0.0 ? "0" : String.format(Locale.US, "%.1f", val);
} else {
croissanceRevenus = "0";
}
} else {
revenusGlobaux = "0 FCFA";
croissanceRevenus = "0";
@@ -212,10 +251,12 @@ public class SuperAdminBean implements Serializable {
try {
Map<String, Object> stats = auditService.getStatistiques();
if (stats != null) {
Object activitesJour = stats.get("activitesAujourdhui");
activiteJournaliere = activitesJour instanceof Number ? ((Number) activitesJour).intValue() : 0;
Object alertes = stats.get("alertes");
alertesCount = alertes instanceof Number ? ((Number) alertes).intValue() : 0;
// "total" = nombre total d'actions enregistrées (proxy activité)
Object total = stats.get("total");
activiteJournaliere = total instanceof Number ? ((Number) total).intValue() : 0;
// "errors" = erreurs critiques enregistrées (proxy alertes système)
Object errors = stats.get("errors");
alertesCount = errors instanceof Number ? ((Number) errors).intValue() : 0;
} else {
activiteJournaliere = 0;
alertesCount = 0;
@@ -230,11 +271,11 @@ public class SuperAdminBean implements Serializable {
private void initializeSouscriptionKPIs() {
try {
List<AbonnementResponse> souscriptions = souscriptionService.listerToutes(null, 0, 1000);
List<SouscriptionStatutResponse> souscriptions = souscriptionService.listerToutes(null, 0, 1000);
if (souscriptions != null) {
totalSouscriptions = souscriptions.size();
souscriptionsActives = (int) souscriptions.stream()
.filter(s -> s.getStatut() == dev.lions.unionflow.server.api.enums.abonnement.StatutAbonnement.ACTIF)
.filter(s -> "ACTIVE".equals(s.getStatut()))
.count();
LocalDate dans30Jours = LocalDate.now().plusDays(30);
souscriptionsExpirantSous30Jours = (int) souscriptions.stream()
@@ -293,22 +334,22 @@ public class SuperAdminBean implements Serializable {
private void initializeEntites() {
topEntites = new ArrayList<>();
try {
PagedResponse<OrganisationResponse> response = associationService.listerToutes(0, 1000);
List<OrganisationResponse> associations = (response != null && response.getData() != null) ? response.getData()
PagedResponse<OrganisationSummaryResponse> response = organisationService.listerToutes(0, 50);
List<OrganisationSummaryResponse> associations = (response != null && response.getData() != null) ? response.getData()
: new ArrayList<>();
topEntites = associations.stream()
.sorted((a1, a2) -> {
int m1 = a1.getNombreMembres() != null ? a1.getNombreMembres() : 0;
int m2 = a2.getNombreMembres() != null ? a2.getNombreMembres() : 0;
int m1 = a1.nombreMembres() != null ? a1.nombreMembres() : 0;
int m2 = a2.nombreMembres() != null ? a2.nombreMembres() : 0;
return Integer.compare(m2, m1);
})
.limit(5)
.map(a -> {
Entite entite = new Entite();
entite.setId(a.getId());
entite.setNom(a.getNom());
entite.setTypeEntite(a.getTypeOrganisation());
entite.setNombreMembres(a.getNombreMembres() != null ? a.getNombreMembres() : 0);
entite.setId(a.id());
entite.setNom(a.nom());
entite.setTypeEntite(a.typeOrganisation());
entite.setNombreMembres(a.nombreMembres() != null ? a.nombreMembres() : 0);
return entite;
})
.collect(java.util.stream.Collectors.toList());
@@ -319,52 +360,47 @@ public class SuperAdminBean implements Serializable {
private void initializeRepartitionTypes() {
repartitionTypes = new ArrayList<>();
try {
PagedResponse<OrganisationResponse> response = associationService.listerToutes(0, 1000);
List<OrganisationResponse> associations = (response != null && response.getData() != null) ? response.getData()
: new ArrayList<>();
if (associations == null || associations.isEmpty())
return;
java.util.Map<String, Long> parType = associations.stream()
.filter(a -> a.getTypeOrganisation() != null && !a.getTypeOrganisation().isBlank())
.collect(java.util.stream.Collectors.groupingBy(OrganisationResponse::getTypeOrganisation,
java.util.stream.Collectors.counting()));
int total = associations.size();
String[] couleurs = { "blue", "green", "orange", "purple", "teal" };
int idx = 0;
for (java.util.Map.Entry<String, Long> entry : parType.entrySet()) {
TypeEntite typeEntite = new TypeEntite();
typeEntite.setNom(entry.getKey());
typeEntite.setDescription(entry.getKey());
typeEntite.setNombre(entry.getValue().intValue());
typeEntite.setPourcentage(total > 0 ? (entry.getValue().intValue() * 100) / total : 0);
typeEntite.setIcone("pi-building");
typeEntite.setCouleurBg(idx < couleurs.length ? couleurs[idx] : "secondary");
typeEntite.setCouleurTexte("white");
repartitionTypes.add(typeEntite);
idx++;
}
} catch (Exception e) {
LOG.warnf(e, "Impossible de calculer la répartition des types");
if (orgStats == null || orgStats.repartitionParType == null || orgStats.repartitionParType.isEmpty()) {
return;
}
int total = orgStats.totalAssociations != null ? orgStats.totalAssociations.intValue() : 0;
String[] couleurs = { "blue", "green", "orange", "purple", "teal" };
int idx = 0;
for (java.util.Map.Entry<String, Long> entry : orgStats.repartitionParType.entrySet()) {
TypeEntite typeEntite = new TypeEntite();
typeEntite.setNom(entry.getKey());
typeEntite.setDescription(entry.getKey());
typeEntite.setNombre(entry.getValue().intValue());
typeEntite.setPourcentage(total > 0 ? (entry.getValue().intValue() * 100) / total : 0);
typeEntite.setIcone("pi-building");
typeEntite.setCouleurBg(idx < couleurs.length ? couleurs[idx] : "secondary");
typeEntite.setCouleurTexte("white");
repartitionTypes.add(typeEntite);
idx++;
}
}
private void initializeActivites() {
activitesRecentes = new ArrayList<>();
try {
Map<String, Object> auditResult = auditService.listerTous(0, 10, "date", "desc");
if (auditResult != null && auditResult.containsKey("content")) {
// sortBy=dateHeure (nom réel du champ), sortOrder=desc
Map<String, Object> auditResult = auditService.listerTous(0, 10, "dateHeure", "desc");
if (auditResult != null && auditResult.containsKey("data")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> entries = (List<Map<String, Object>>) auditResult.get("content");
List<Map<String, Object>> entries = (List<Map<String, Object>>) auditResult.get("data");
if (entries != null) {
for (Map<String, Object> entry : entries) {
Activite activite = new Activite();
activite.setId(UUID.randomUUID());
activite.setDescription(entry.getOrDefault("action", "").toString());
// typeAction = action métier, description = détail lisible
Object desc = entry.get("description");
Object typeAction = entry.get("typeAction");
activite.setDescription(desc != null ? desc.toString()
: (typeAction != null ? typeAction.toString() : ""));
activite.setEntite(entry.getOrDefault("module", "").toString());
activite.setUtilisateur(entry.getOrDefault("utilisateur", "").toString());
activite.setDetails(entry.getOrDefault("details", "").toString());
Object dateObj = entry.get("date");
Object dateObj = entry.get("dateHeure");
activite.setDate(dateObj != null ? dateObj.toString() : "");
activite.setIcone("pi-history");
activitesRecentes.add(activite);
@@ -423,6 +459,14 @@ public class SuperAdminBean implements Serializable {
return OUTCOME_SUPER_ADMIN_ACTIVITE + "?faces-redirect=true";
}
public String auditSysteme() {
return OUTCOME_SUPER_ADMIN_AUDIT + "?faces-redirect=true";
}
public String backup() {
return OUTCOME_SUPER_ADMIN_BACKUP + "?faces-redirect=true";
}
public void exporterRapportFinancier() {
LOG.info("Export du rapport financier généré");
}
@@ -615,15 +659,15 @@ public class SuperAdminBean implements Serializable {
}
public String getTauxConversionFormat() {
return String.format("%.1f%%", tauxConversion);
return String.format(Locale.US, "%.1f%%", tauxConversion);
}
public String getDisponibiliteSystemeFormat() {
return String.format("%.1f%%", disponibiliteSysteme);
return String.format(Locale.US, "%.1f%%", disponibiliteSysteme);
}
public String getSatisfactionClientFormat() {
return String.format("%.1f/5", satisfactionClient);
return String.format(Locale.US, "%.1f/5", satisfactionClient);
}
public List<Alerte> getAlertesRecentes() {