feat(sprint-8 web 2026-04-25): pages PrimeFaces conformité (compliance dashboard + rapports trimestriels + PI-SPI readiness)
Front-end web JSF/PrimeFaces pour exposer les features backend Sprints 3, 5, 7 aux compliance officers et controleurs internes. DTOs locaux (miroirs JSON, @JsonIgnoreProperties) - ComplianceSnapshotDto + ConformiteIndicateurDto (P1-NEW-7) - RapportTrimestrielDto + helpers estDraft/estSigne/estArchive (P2-NEW-3) - PispiReadinessDto + CheckResultDto + helpers estReady/estBlocked (P1-NEW-15) REST clients @RegisterRestClient configKey=unionflow-api - ComplianceDashboardRestClient : getSnapshotCurrent + getSnapshotOf(orgId) - RapportTrimestrielRestClient : lister, generer, signer, archiver, telechargerPdf - PispiReadinessRestClient : getReadiness - AuthHeaderFactory propage le token OIDC Beans @ViewScoped - ConformiteDashboardBean : init + rafraichir + couleurScore + hasAlertes - RapportsTrimestrielsBean : lister, genererRapport, signerSelection, archiverSelection, telechargerPdf via ExternalContext - PispiReadinessBean : rafraichir + gestion HTTP 503 BLOCKED + couleurStatus + couleurCheck Pages XHTML PrimeFaces (template main-template.xhtml, classes Freya) - /pages/secure/conformite/dashboard.xhtml — score global + 9 indicateurs en grille + alertes - /pages/secure/conformite/rapports-trimestriels.xhtml — table DRAFT/SIGNE/ARCHIVE + bouton générer/signer/archiver/PDF - /pages/secure/admin/pispi-readiness.xhtml — 8 checks + blocages/warnings dédiés + statut global Tests (21/21 verts, JUnit5 natif puisque AssertJ non transitif) - ConformiteDashboardBeanTest : 9 tests (couleur score success/warning/danger/secondary, hasAlertes 5 cas) - PispiReadinessBeanTest : 8 tests (couleurStatus READY/DEGRADED/BLOCKED/null, couleurCheck PASS/FAIL × severity, DTO helpers) - RapportTrimestrielDtoTest : 4 tests (estDraft/estSigne/estArchive/inconnu)
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
package dev.lions.unionflow.client.api.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Miroir local du record {@code ComplianceSnapshot} backend (P1-NEW-7).
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record ComplianceSnapshotDto(
|
||||
UUID organisationId,
|
||||
String organisationNom,
|
||||
String referentielComptable,
|
||||
boolean complianceOfficerDesigne,
|
||||
ConformiteIndicateurDto agAnnuelle,
|
||||
ConformiteIndicateurDto rapportAirms,
|
||||
int dirigeantsAvecCmu,
|
||||
BigDecimal tauxKycAJourPct,
|
||||
BigDecimal tauxFormationLbcFtPct,
|
||||
ConformiteIndicateurDto commissaireAuxComptes,
|
||||
ConformiteIndicateurDto fomusCi,
|
||||
BigDecimal couvertureUboPct,
|
||||
int scoreGlobal
|
||||
) {
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record ConformiteIndicateurDto(String statut, String message) {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package dev.lions.unionflow.client.api.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Miroir local du DTO {@code ReadinessReport} backend (P1-NEW-15).
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record PispiReadinessDto(
|
||||
String globalStatus, // READY, DEGRADED, BLOCKED
|
||||
String baseUrl,
|
||||
List<CheckResultDto> checks,
|
||||
List<String> blockingIssues,
|
||||
List<String> warnings
|
||||
) {
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record CheckResultDto(
|
||||
String name,
|
||||
String status, // PASS, FAIL
|
||||
String severity, // BLOCKING, WARNING
|
||||
String message
|
||||
) {}
|
||||
|
||||
public boolean estReady() { return "READY".equals(globalStatus); }
|
||||
public boolean estBlocked() { return "BLOCKED".equals(globalStatus); }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dev.lions.unionflow.client.api.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Miroir local de l'entité {@code RapportTrimestrielControleurInterne} backend (P2-NEW-3).
|
||||
*
|
||||
* <p>Champs JSONB et byte[] omis — récupérés via endpoint PDF dédié.
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record RapportTrimestrielDto(
|
||||
UUID id,
|
||||
UUID organisationId,
|
||||
Integer annee,
|
||||
Integer trimestre,
|
||||
LocalDateTime dateGeneration,
|
||||
String statut,
|
||||
Integer scoreConformite,
|
||||
UUID signataireId,
|
||||
LocalDateTime dateSignature,
|
||||
String hashSha256
|
||||
) {
|
||||
public boolean estDraft() { return "DRAFT".equals(statut); }
|
||||
public boolean estSigne() { return "SIGNE".equals(statut); }
|
||||
public boolean estArchive() { return "ARCHIVE".equals(statut); }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.ComplianceSnapshotDto;
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/compliance/dashboard")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface ComplianceDashboardRestClient {
|
||||
|
||||
/** Snapshot pour l'organisation active (résolue par le backend via header X-Active-Organisation-Id). */
|
||||
@GET
|
||||
ComplianceSnapshotDto getSnapshotCurrent();
|
||||
|
||||
/** Snapshot d'une organisation arbitraire — restreint SUPER_ADMIN backend. */
|
||||
@GET
|
||||
@Path("/{organisationId}")
|
||||
ComplianceSnapshotDto getSnapshotOf(@PathParam("organisationId") UUID organisationId);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.PispiReadinessDto;
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/admin/pispi/readiness")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface PispiReadinessRestClient {
|
||||
|
||||
@GET
|
||||
PispiReadinessDto getReadiness();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.RapportTrimestrielDto;
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/rapports/trimestriel")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface RapportTrimestrielRestClient {
|
||||
|
||||
@GET
|
||||
List<RapportTrimestrielDto> lister(
|
||||
@QueryParam("orgId") UUID orgId,
|
||||
@QueryParam("annee") Integer annee);
|
||||
|
||||
@POST
|
||||
@Path("/generer")
|
||||
RapportTrimestrielDto generer(
|
||||
@QueryParam("orgId") UUID orgId,
|
||||
@QueryParam("annee") int annee,
|
||||
@QueryParam("trimestre") int trimestre);
|
||||
|
||||
@POST
|
||||
@Path("/{id}/signer")
|
||||
RapportTrimestrielDto signer(
|
||||
@PathParam("id") UUID id,
|
||||
@QueryParam("signataireId") UUID signataireId);
|
||||
|
||||
@POST
|
||||
@Path("/{id}/archiver")
|
||||
RapportTrimestrielDto archiver(@PathParam("id") UUID id);
|
||||
|
||||
@GET
|
||||
@Path("/{id}/pdf")
|
||||
@Produces("application/pdf")
|
||||
byte[] telechargerPdf(@PathParam("id") UUID id);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.ComplianceSnapshotDto;
|
||||
import dev.lions.unionflow.client.service.ComplianceDashboardRestClient;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.io.Serializable;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bean du tableau de bord de conformité (Sprint 8 ⇄ backend P1-NEW-7).
|
||||
*
|
||||
* <p>Affiche le snapshot de l'organisation active : score global, indicateurs AG/AIRMS/CMU/KYC/UBO,
|
||||
* alertes critiques. Refresh manuel par bouton.
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class ConformiteDashboardBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(ConformiteDashboardBean.class);
|
||||
|
||||
@Inject @RestClient
|
||||
ComplianceDashboardRestClient client;
|
||||
|
||||
private ComplianceSnapshotDto snapshot;
|
||||
private String erreur;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
rafraichir();
|
||||
}
|
||||
|
||||
public void rafraichir() {
|
||||
erreur = null;
|
||||
try {
|
||||
snapshot = client.getSnapshotCurrent();
|
||||
LOG.infof("Snapshot conformité chargé : score=%d", snapshot.scoreGlobal());
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Échec chargement compliance dashboard : %s", e.getMessage());
|
||||
erreur = "Impossible de charger le tableau de bord : " + e.getMessage();
|
||||
addMessage(FacesMessage.SEVERITY_ERROR, "Erreur", erreur);
|
||||
snapshot = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCouleurScore() {
|
||||
if (snapshot == null) return "secondary";
|
||||
int s = snapshot.scoreGlobal();
|
||||
if (s >= 80) return "success";
|
||||
if (s >= 60) return "warning";
|
||||
return "danger";
|
||||
}
|
||||
|
||||
public boolean hasAlertes() {
|
||||
if (snapshot == null) return false;
|
||||
return !snapshot.complianceOfficerDesigne()
|
||||
|| "RETARD".equals(snapshot.agAnnuelle().statut())
|
||||
|| snapshot.scoreGlobal() < 60;
|
||||
}
|
||||
|
||||
private void addMessage(FacesMessage.Severity sev, String summary, String detail) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(sev, summary, detail));
|
||||
}
|
||||
|
||||
public ComplianceSnapshotDto getSnapshot() { return snapshot; }
|
||||
public String getErreur() { return erreur; }
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.PispiReadinessDto;
|
||||
import dev.lions.unionflow.client.service.PispiReadinessRestClient;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.io.Serializable;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bean d'inspection PI-SPI Readiness (Sprint 8 ⇄ P1-NEW-15).
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class PispiReadinessBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(PispiReadinessBean.class);
|
||||
|
||||
@Inject @RestClient
|
||||
PispiReadinessRestClient client;
|
||||
|
||||
private PispiReadinessDto readiness;
|
||||
private String erreur;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
rafraichir();
|
||||
}
|
||||
|
||||
public void rafraichir() {
|
||||
erreur = null;
|
||||
try {
|
||||
readiness = client.getReadiness();
|
||||
LOG.infof("PI-SPI readiness chargé : %s (%d blocking, %d warnings)",
|
||||
readiness.globalStatus(),
|
||||
readiness.blockingIssues() == null ? 0 : readiness.blockingIssues().size(),
|
||||
readiness.warnings() == null ? 0 : readiness.warnings().size());
|
||||
} catch (jakarta.ws.rs.WebApplicationException wae) {
|
||||
// Backend renvoie 503 quand BLOCKED — on récupère quand même le body
|
||||
try {
|
||||
readiness = wae.getResponse().readEntity(PispiReadinessDto.class);
|
||||
LOG.infof("PI-SPI readiness BLOCKED — body décodé");
|
||||
} catch (Exception parseEx) {
|
||||
handleError("Décodage réponse readiness échoué", parseEx);
|
||||
readiness = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleError("Chargement PI-SPI readiness échoué", e);
|
||||
readiness = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCouleurStatus() {
|
||||
if (readiness == null) return "secondary";
|
||||
return switch (readiness.globalStatus()) {
|
||||
case "READY" -> "success";
|
||||
case "DEGRADED" -> "warning";
|
||||
case "BLOCKED" -> "danger";
|
||||
default -> "secondary";
|
||||
};
|
||||
}
|
||||
|
||||
public String getCouleurCheck(String severity, String status) {
|
||||
if ("PASS".equals(status)) return "success";
|
||||
return "BLOCKING".equals(severity) ? "danger" : "warning";
|
||||
}
|
||||
|
||||
private void handleError(String summary, Exception e) {
|
||||
LOG.warnf("%s : %s", summary, e.getMessage());
|
||||
erreur = summary + " — " + e.getMessage();
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", erreur));
|
||||
}
|
||||
|
||||
public PispiReadinessDto getReadiness() { return readiness; }
|
||||
public String getErreur() { return erreur; }
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.RapportTrimestrielDto;
|
||||
import dev.lions.unionflow.client.service.RapportTrimestrielRestClient;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.ExternalContext;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.time.Year;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bean liste/actions sur les rapports trimestriels Contrôleur Interne (Sprint 8 ⇄ P2-NEW-3).
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class RapportsTrimestrielsBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(RapportsTrimestrielsBean.class);
|
||||
|
||||
@Inject @RestClient
|
||||
RapportTrimestrielRestClient client;
|
||||
|
||||
private List<RapportTrimestrielDto> rapports = Collections.emptyList();
|
||||
private int annee = Year.now().getValue();
|
||||
private int trimestreCible = 1;
|
||||
private RapportTrimestrielDto selection;
|
||||
private String erreur;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
rafraichir();
|
||||
}
|
||||
|
||||
public void rafraichir() {
|
||||
erreur = null;
|
||||
try {
|
||||
rapports = client.lister(null, annee);
|
||||
LOG.infof("Liste rapports trimestriels chargée pour année %d : %d entrée(s)",
|
||||
annee, rapports.size());
|
||||
} catch (Exception e) {
|
||||
handleError("Chargement des rapports échoué", e);
|
||||
rapports = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public void genererRapport() {
|
||||
erreur = null;
|
||||
try {
|
||||
var nouveau = client.generer(null, annee, trimestreCible);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Rapport généré",
|
||||
"Trimestre " + nouveau.trimestre() + "/" + nouveau.annee()
|
||||
+ " — score " + nouveau.scoreConformite());
|
||||
rafraichir();
|
||||
} catch (Exception e) {
|
||||
handleError("Génération du rapport échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void signerSelection(UUID signataireId) {
|
||||
if (selection == null) {
|
||||
addMessage(FacesMessage.SEVERITY_WARN, "Aucune sélection",
|
||||
"Sélectionnez un rapport DRAFT à signer");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
client.signer(selection.id(), signataireId);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Signé",
|
||||
"Rapport " + selection.annee() + "/T" + selection.trimestre() + " signé");
|
||||
rafraichir();
|
||||
} catch (Exception e) {
|
||||
handleError("Signature échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void archiverSelection() {
|
||||
if (selection == null) {
|
||||
addMessage(FacesMessage.SEVERITY_WARN, "Aucune sélection",
|
||||
"Sélectionnez un rapport SIGNE à archiver");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
client.archiver(selection.id());
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Archivé",
|
||||
"Rapport " + selection.annee() + "/T" + selection.trimestre() + " archivé");
|
||||
rafraichir();
|
||||
} catch (Exception e) {
|
||||
handleError("Archivage échoué", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void telechargerPdf(RapportTrimestrielDto r) {
|
||||
if (r == null) return;
|
||||
try {
|
||||
byte[] pdf = client.telechargerPdf(r.id());
|
||||
String filename = String.format("rapport-trim-%d-T%d.pdf", r.annee(), r.trimestre());
|
||||
|
||||
FacesContext fc = FacesContext.getCurrentInstance();
|
||||
ExternalContext ec = fc.getExternalContext();
|
||||
ec.responseReset();
|
||||
ec.setResponseContentType("application/pdf");
|
||||
ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||
ec.setResponseContentLength(pdf.length);
|
||||
try (OutputStream os = ec.getResponseOutputStream()) {
|
||||
os.write(pdf);
|
||||
}
|
||||
fc.responseComplete();
|
||||
} catch (IOException e) {
|
||||
handleError("Téléchargement PDF échoué", e);
|
||||
} catch (Exception e) {
|
||||
handleError("Téléchargement PDF échoué", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleError(String summary, Exception e) {
|
||||
LOG.warnf("%s : %s", summary, e.getMessage());
|
||||
erreur = summary + " — " + e.getMessage();
|
||||
addMessage(FacesMessage.SEVERITY_ERROR, "Erreur", erreur);
|
||||
}
|
||||
|
||||
private void addMessage(FacesMessage.Severity sev, String summary, String detail) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(sev, summary, detail));
|
||||
}
|
||||
|
||||
public List<RapportTrimestrielDto> getRapports() { return rapports; }
|
||||
public int getAnnee() { return annee; }
|
||||
public void setAnnee(int annee) { this.annee = annee; }
|
||||
public int getTrimestreCible() { return trimestreCible; }
|
||||
public void setTrimestreCible(int trimestreCible) { this.trimestreCible = trimestreCible; }
|
||||
public RapportTrimestrielDto getSelection() { return selection; }
|
||||
public void setSelection(RapportTrimestrielDto selection) { this.selection = selection; }
|
||||
public String getErreur() { return erreur; }
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">UnionFlow - PI-SPI Readiness</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="text-900 font-medium text-3xl m-0">PI-SPI Readiness</h2>
|
||||
<p class="text-600 mt-1 mb-0">
|
||||
Vérification des pré-requis intégration PI-SPI BCEAO avant activation production.
|
||||
</p>
|
||||
</div>
|
||||
<p:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
action="#{pispiReadinessBean.rafraichir}"
|
||||
update=":form-readiness"
|
||||
styleClass="p-button-outlined" />
|
||||
</div>
|
||||
|
||||
<h:form id="form-readiness">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<ui:fragment rendered="#{pispiReadinessBean.readiness != null}">
|
||||
<!-- Statut global -->
|
||||
<div class="surface-100 border-round p-4 mb-4 text-center">
|
||||
<div class="text-700 mb-2">Statut global</div>
|
||||
<p:tag value="#{pispiReadinessBean.readiness.globalStatus}"
|
||||
severity="#{pispiReadinessBean.couleurStatus}"
|
||||
style="font-size: 1.5rem; padding: 0.5rem 1.5rem;" />
|
||||
<div class="text-600 mt-3">
|
||||
Base URL : <span class="font-mono">#{pispiReadinessBean.readiness.baseUrl}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blocages -->
|
||||
<p:fieldset legend="Blocages critiques"
|
||||
rendered="#{not empty pispiReadinessBean.readiness.blockingIssues}"
|
||||
styleClass="mb-3 border-red-300">
|
||||
<ul class="m-0">
|
||||
<ui:repeat value="#{pispiReadinessBean.readiness.blockingIssues}" var="b">
|
||||
<li class="text-red-600">#{b}</li>
|
||||
</ui:repeat>
|
||||
</ul>
|
||||
</p:fieldset>
|
||||
|
||||
<!-- Warnings -->
|
||||
<p:fieldset legend="Avertissements (non bloquants)"
|
||||
rendered="#{not empty pispiReadinessBean.readiness.warnings}"
|
||||
styleClass="mb-3 border-orange-300">
|
||||
<ul class="m-0">
|
||||
<ui:repeat value="#{pispiReadinessBean.readiness.warnings}" var="w">
|
||||
<li class="text-orange-600">#{w}</li>
|
||||
</ui:repeat>
|
||||
</ul>
|
||||
</p:fieldset>
|
||||
|
||||
<!-- Détail des checks -->
|
||||
<p:dataTable value="#{pispiReadinessBean.readiness.checks}"
|
||||
var="c"
|
||||
emptyMessage="Aucun check disponible">
|
||||
<p:column headerText="Vérification">
|
||||
<h:outputText value="#{c.name}" styleClass="font-mono" />
|
||||
</p:column>
|
||||
<p:column headerText="Sévérité">
|
||||
<p:tag value="#{c.severity}"
|
||||
severity="#{c.severity eq 'BLOCKING' ? 'danger' : 'warning'}" />
|
||||
</p:column>
|
||||
<p:column headerText="Statut">
|
||||
<p:tag value="#{c.status}"
|
||||
severity="#{c.status eq 'PASS' ? 'success' : pispiReadinessBean.getCouleurCheck(c.severity, c.status)}"
|
||||
icon="#{c.status eq 'PASS' ? 'pi pi-check' : 'pi pi-times'}" />
|
||||
</p:column>
|
||||
<p:column headerText="Message">
|
||||
<h:outputText value="#{c.message}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</ui:fragment>
|
||||
|
||||
<ui:fragment rendered="#{pispiReadinessBean.readiness == null}">
|
||||
<div class="text-center text-600 p-5">
|
||||
Impossible de récupérer le rapport readiness.
|
||||
</div>
|
||||
</ui:fragment>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">UnionFlow - Tableau de bord conformité</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="text-900 font-medium text-3xl m-0">Tableau de bord conformité</h2>
|
||||
<p class="text-600 mt-1 mb-0">
|
||||
Indicateurs BCEAO Instr. 001-03-2025 / ARTCI / OHADA / AIRMS
|
||||
</p>
|
||||
</div>
|
||||
<p:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
action="#{conformiteDashboardBean.rafraichir}"
|
||||
update=":form-conformite"
|
||||
styleClass="p-button-outlined" />
|
||||
</div>
|
||||
|
||||
<h:form id="form-conformite">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<ui:fragment rendered="#{conformiteDashboardBean.snapshot != null}">
|
||||
<!-- Score global -->
|
||||
<div class="grid mb-4">
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="surface-100 border-round p-4 text-center">
|
||||
<div class="text-700 mb-2">Score conformité global</div>
|
||||
<div class="text-#{conformiteDashboardBean.couleurScore eq 'success' ? 'green' : (conformiteDashboardBean.couleurScore eq 'warning' ? 'orange' : 'red')}-600 font-bold"
|
||||
style="font-size: 3.5rem; line-height: 1;">
|
||||
#{conformiteDashboardBean.snapshot.scoreGlobal}
|
||||
</div>
|
||||
<div class="text-600 mt-1">/ 100</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-8">
|
||||
<div class="surface-100 border-round p-4 h-full">
|
||||
<div class="text-700 mb-2 font-bold">Organisation</div>
|
||||
<div class="text-900 text-xl">#{conformiteDashboardBean.snapshot.organisationNom}</div>
|
||||
<div class="text-600 mt-2">
|
||||
Référentiel comptable : <span class="font-semibold">#{conformiteDashboardBean.snapshot.referentielComptable}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Indicateurs détaillés -->
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="flex justify-content-between">
|
||||
<span class="text-700">Compliance Officer</span>
|
||||
<p:tag value="#{conformiteDashboardBean.snapshot.complianceOfficerDesigne ? 'Désigné' : 'ABSENT'}"
|
||||
severity="#{conformiteDashboardBean.snapshot.complianceOfficerDesigne ? 'success' : 'danger'}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="flex justify-content-between">
|
||||
<span class="text-700">AG annuelle</span>
|
||||
<p:tag value="#{conformiteDashboardBean.snapshot.agAnnuelle.statut}"
|
||||
severity="#{conformiteDashboardBean.snapshot.agAnnuelle.statut eq 'OK' ? 'success' : (conformiteDashboardBean.snapshot.agAnnuelle.statut eq 'RETARD' ? 'danger' : 'warning')}" />
|
||||
</div>
|
||||
<p class="text-600 text-sm mt-2 m-0">#{conformiteDashboardBean.snapshot.agAnnuelle.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="flex justify-content-between">
|
||||
<span class="text-700">Rapport AIRMS</span>
|
||||
<p:tag value="#{conformiteDashboardBean.snapshot.rapportAirms.statut}"
|
||||
severity="#{conformiteDashboardBean.snapshot.rapportAirms.statut eq 'OK' ? 'success' : 'warning'}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">Dirigeants enrôlés CMU</div>
|
||||
<div class="text-900 text-2xl mt-1">#{conformiteDashboardBean.snapshot.dirigeantsAvecCmu}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">Taux KYC à jour</div>
|
||||
<div class="text-900 text-2xl mt-1">#{conformiteDashboardBean.snapshot.tauxKycAJourPct} %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">Taux formation LBC/FT</div>
|
||||
<div class="text-900 text-2xl mt-1">#{conformiteDashboardBean.snapshot.tauxFormationLbcFtPct} %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">Couverture UBO</div>
|
||||
<div class="text-900 text-2xl mt-1">#{conformiteDashboardBean.snapshot.couvertureUboPct} %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">Commissaire aux comptes</div>
|
||||
<p:tag value="#{conformiteDashboardBean.snapshot.commissaireAuxComptes.statut}"
|
||||
styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="text-700">FOMUS-CI</div>
|
||||
<p:tag value="#{conformiteDashboardBean.snapshot.fomusCi.statut}"
|
||||
styleClass="mt-1" />
|
||||
<p class="text-600 text-sm mt-2 m-0">#{conformiteDashboardBean.snapshot.fomusCi.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:fieldset legend="Alertes critiques"
|
||||
rendered="#{conformiteDashboardBean.hasAlertes}"
|
||||
styleClass="mt-4 surface-100">
|
||||
<ul class="m-0">
|
||||
<li class="text-red-600" jsf:rendered="#{not conformiteDashboardBean.snapshot.complianceOfficerDesigne}">
|
||||
Compliance Officer non désigné — Instruction BCEAO 001-03-2025 obligatoire
|
||||
</li>
|
||||
<li class="text-red-600" jsf:rendered="#{conformiteDashboardBean.snapshot.agAnnuelle.statut eq 'RETARD'}">
|
||||
AG annuelle en retard — échéance 30 juin
|
||||
</li>
|
||||
<li class="text-orange-600" jsf:rendered="#{conformiteDashboardBean.snapshot.scoreGlobal lt 60}">
|
||||
Score global < 60 % — risque inspection BCEAO/ARTCI
|
||||
</li>
|
||||
</ul>
|
||||
</p:fieldset>
|
||||
</ui:fragment>
|
||||
|
||||
<ui:fragment rendered="#{conformiteDashboardBean.snapshot == null}">
|
||||
<p:messages />
|
||||
<div class="text-center text-600 p-5">
|
||||
Aucune donnée de conformité disponible.
|
||||
</div>
|
||||
</ui:fragment>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,125 @@
|
||||
<!DOCTYPE html>
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">UnionFlow - Rapports trimestriels Contrôleur Interne</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="text-900 font-medium text-3xl m-0">Rapports trimestriels — Contrôleur Interne</h2>
|
||||
<p class="text-600 mt-1 mb-0">
|
||||
Source AG, inspections BCEAO/ARTCI. Cycle DRAFT → SIGNE → ARCHIVE.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h:form id="form-rapports">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<!-- Filtres + génération -->
|
||||
<p:panel header="Génération rapport" styleClass="mb-3">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<p:outputLabel for="annee" value="Année" />
|
||||
<p:inputNumber id="annee"
|
||||
value="#{rapportsTrimestrielsBean.annee}"
|
||||
minValue="2024" maxValue="2099"
|
||||
thousandSeparator=""
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<p:outputLabel for="trim" value="Trimestre" />
|
||||
<p:selectOneMenu id="trim"
|
||||
value="#{rapportsTrimestrielsBean.trimestreCible}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="T1 (Jan-Mar)" itemValue="1" />
|
||||
<f:selectItem itemLabel="T2 (Avr-Jun)" itemValue="2" />
|
||||
<f:selectItem itemLabel="T3 (Jul-Sep)" itemValue="3" />
|
||||
<f:selectItem itemLabel="T4 (Oct-Déc)" itemValue="4" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="col-12 md:col-3 flex align-items-end">
|
||||
<p:commandButton value="Générer"
|
||||
icon="pi pi-plus"
|
||||
action="#{rapportsTrimestrielsBean.genererRapport}"
|
||||
update="form-rapports"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-3 flex align-items-end">
|
||||
<p:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
action="#{rapportsTrimestrielsBean.rafraichir}"
|
||||
update="form-rapports"
|
||||
styleClass="p-button-outlined w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
<!-- Tableau -->
|
||||
<p:dataTable id="tbl-rapports"
|
||||
value="#{rapportsTrimestrielsBean.rapports}"
|
||||
var="r"
|
||||
selection="#{rapportsTrimestrielsBean.selection}"
|
||||
rowKey="#{r.id}"
|
||||
selectionMode="single"
|
||||
emptyMessage="Aucun rapport pour cette année"
|
||||
styleClass="mb-3">
|
||||
|
||||
<p:column headerText="Trimestre">
|
||||
<h:outputText value="T#{r.trimestre} / #{r.annee}" />
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Date génération">
|
||||
<h:outputText value="#{r.dateGeneration}" />
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Score">
|
||||
<p:tag value="#{r.scoreConformite}/100"
|
||||
severity="#{r.scoreConformite ge 80 ? 'success' : (r.scoreConformite ge 60 ? 'warning' : 'danger')}" />
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Statut">
|
||||
<p:tag value="#{r.statut}"
|
||||
severity="#{r.statut eq 'ARCHIVE' ? 'success' : (r.statut eq 'SIGNE' ? 'info' : 'warning')}" />
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Hash signature">
|
||||
<h:outputText value="#{r.hashSha256.substring(0, 16)}..." rendered="#{r.hashSha256 != null}" />
|
||||
<h:outputText value="—" rendered="#{r.hashSha256 == null}" />
|
||||
</p:column>
|
||||
|
||||
<p:column headerText="Actions" style="width: 120px;">
|
||||
<p:commandButton icon="pi pi-download"
|
||||
title="Télécharger PDF"
|
||||
action="#{rapportsTrimestrielsBean.telechargerPdf(r)}"
|
||||
ajax="false"
|
||||
styleClass="p-button-text" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<p:commandButton value="Signer (sélection)"
|
||||
icon="pi pi-check"
|
||||
action="#{rapportsTrimestrielsBean.signerSelection(userSession.currentUser != null ? userSession.currentUser.id : null)}"
|
||||
update="form-rapports"
|
||||
styleClass="p-button-success" />
|
||||
<p:commandButton value="Archiver (sélection)"
|
||||
icon="pi pi-lock"
|
||||
action="#{rapportsTrimestrielsBean.archiverSelection}"
|
||||
update="form-rapports"
|
||||
styleClass="p-button-secondary" />
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.lions.unionflow.client.api.dto;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RapportTrimestrielDtoTest {
|
||||
|
||||
private RapportTrimestrielDto build(String statut) {
|
||||
return new RapportTrimestrielDto(
|
||||
UUID.randomUUID(), UUID.randomUUID(), 2026, 1,
|
||||
LocalDateTime.now(), statut, 75, null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDraft / estSigne / estArchive — DRAFT")
|
||||
void draft() {
|
||||
var r = build("DRAFT");
|
||||
assertTrue(r.estDraft());
|
||||
assertFalse(r.estSigne());
|
||||
assertFalse(r.estArchive());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDraft / estSigne / estArchive — SIGNE")
|
||||
void signe() {
|
||||
var r = build("SIGNE");
|
||||
assertFalse(r.estDraft());
|
||||
assertTrue(r.estSigne());
|
||||
assertFalse(r.estArchive());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("estDraft / estSigne / estArchive — ARCHIVE")
|
||||
void archive() {
|
||||
var r = build("ARCHIVE");
|
||||
assertFalse(r.estDraft());
|
||||
assertFalse(r.estSigne());
|
||||
assertTrue(r.estArchive());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Statut inconnu → tous false")
|
||||
void inconnu() {
|
||||
var r = build("AUTRE");
|
||||
assertFalse(r.estDraft());
|
||||
assertFalse(r.estSigne());
|
||||
assertFalse(r.estArchive());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.ComplianceSnapshotDto;
|
||||
import dev.lions.unionflow.client.api.dto.ComplianceSnapshotDto.ConformiteIndicateurDto;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests purs logique du bean conformité — pas d'invocation REST réelle.
|
||||
*
|
||||
* @since 2026-04-25 (Sprint 8)
|
||||
*/
|
||||
class ConformiteDashboardBeanTest {
|
||||
|
||||
private ConformiteDashboardBean bean;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bean = new ConformiteDashboardBean();
|
||||
}
|
||||
|
||||
private void setSnapshot(ConformiteDashboardBean b, ComplianceSnapshotDto s) throws Exception {
|
||||
Field f = ConformiteDashboardBean.class.getDeclaredField("snapshot");
|
||||
f.setAccessible(true);
|
||||
f.set(b, s);
|
||||
}
|
||||
|
||||
private ComplianceSnapshotDto snapshotAvec(int score, boolean officer, String agStatut) {
|
||||
return new ComplianceSnapshotDto(
|
||||
UUID.randomUUID(), "Test Mutuelle", "SYSCOHADA",
|
||||
officer,
|
||||
new ConformiteIndicateurDto(agStatut, ""),
|
||||
new ConformiteIndicateurDto("OK", ""),
|
||||
3, new BigDecimal("80"), new BigDecimal("70"),
|
||||
new ConformiteIndicateurDto("OPTIONNEL", ""),
|
||||
new ConformiteIndicateurDto("EN_VEILLE", ""),
|
||||
new BigDecimal("60"),
|
||||
score);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurScore — score >=80 → success")
|
||||
void couleurSuccess() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(85, true, "OK"));
|
||||
assertEquals("success", bean.getCouleurScore());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurScore — 60..79 → warning")
|
||||
void couleurWarning() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(70, true, "OK"));
|
||||
assertEquals("warning", bean.getCouleurScore());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurScore — <60 → danger")
|
||||
void couleurDanger() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(45, true, "OK"));
|
||||
assertEquals("danger", bean.getCouleurScore());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurScore — snapshot null → secondary")
|
||||
void couleurSecondaire() {
|
||||
assertEquals("secondary", bean.getCouleurScore());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAlertes — score 85 + officer OK + AG OK → false")
|
||||
void pasDAlertes() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(85, true, "OK"));
|
||||
assertFalse(bean.hasAlertes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAlertes — officer absent → true")
|
||||
void alerteOfficerAbsent() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(85, false, "OK"));
|
||||
assertTrue(bean.hasAlertes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAlertes — AG en RETARD → true")
|
||||
void alerteAgRetard() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(85, true, "RETARD"));
|
||||
assertTrue(bean.hasAlertes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAlertes — score <60 → true")
|
||||
void alerteScoreFaible() throws Exception {
|
||||
setSnapshot(bean, snapshotAvec(50, true, "OK"));
|
||||
assertTrue(bean.hasAlertes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("hasAlertes — snapshot null → false")
|
||||
void hasAlertesNull() {
|
||||
assertFalse(bean.hasAlertes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import dev.lions.unionflow.client.api.dto.PispiReadinessDto;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PispiReadinessBeanTest {
|
||||
|
||||
private PispiReadinessBean bean;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bean = new PispiReadinessBean();
|
||||
}
|
||||
|
||||
private void setReadiness(PispiReadinessDto r) throws Exception {
|
||||
Field f = PispiReadinessBean.class.getDeclaredField("readiness");
|
||||
f.setAccessible(true);
|
||||
f.set(bean, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurStatus — null → secondary")
|
||||
void couleurNull() {
|
||||
assertEquals("secondary", bean.getCouleurStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurStatus — READY → success")
|
||||
void couleurReady() throws Exception {
|
||||
setReadiness(new PispiReadinessDto("READY", "url", List.of(), List.of(), List.of()));
|
||||
assertEquals("success", bean.getCouleurStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurStatus — DEGRADED → warning")
|
||||
void couleurDegraded() throws Exception {
|
||||
setReadiness(new PispiReadinessDto("DEGRADED", "url", List.of(), List.of(), List.of()));
|
||||
assertEquals("warning", bean.getCouleurStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurStatus — BLOCKED → danger")
|
||||
void couleurBlocked() throws Exception {
|
||||
setReadiness(new PispiReadinessDto("BLOCKED", "url", List.of(), List.of(), List.of()));
|
||||
assertEquals("danger", bean.getCouleurStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurCheck — PASS → success quel que soit severity")
|
||||
void couleurCheckPass() {
|
||||
assertEquals("success", bean.getCouleurCheck("BLOCKING", "PASS"));
|
||||
assertEquals("success", bean.getCouleurCheck("WARNING", "PASS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurCheck — FAIL BLOCKING → danger")
|
||||
void couleurCheckFailBlocking() {
|
||||
assertEquals("danger", bean.getCouleurCheck("BLOCKING", "FAIL"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("couleurCheck — FAIL WARNING → warning")
|
||||
void couleurCheckFailWarning() {
|
||||
assertEquals("warning", bean.getCouleurCheck("WARNING", "FAIL"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DTO estReady / estBlocked")
|
||||
void dtoStateHelpers() {
|
||||
var ready = new PispiReadinessDto("READY", "u", List.of(), List.of(), List.of());
|
||||
var blocked = new PispiReadinessDto("BLOCKED", "u", List.of(), List.of(), List.of());
|
||||
assertTrue(ready.estReady());
|
||||
assertFalse(ready.estBlocked());
|
||||
assertFalse(blocked.estReady());
|
||||
assertTrue(blocked.estBlocked());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user