diff --git a/src/main/java/dev/lions/unionflow/client/api/dto/ComplianceSnapshotDto.java b/src/main/java/dev/lions/unionflow/client/api/dto/ComplianceSnapshotDto.java new file mode 100644 index 0000000..537f4e6 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/api/dto/ComplianceSnapshotDto.java @@ -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) {} +} diff --git a/src/main/java/dev/lions/unionflow/client/api/dto/PispiReadinessDto.java b/src/main/java/dev/lions/unionflow/client/api/dto/PispiReadinessDto.java new file mode 100644 index 0000000..458e074 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/api/dto/PispiReadinessDto.java @@ -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 checks, + List blockingIssues, + List 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); } +} diff --git a/src/main/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDto.java b/src/main/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDto.java new file mode 100644 index 0000000..d3538e4 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDto.java @@ -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). + * + *

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); } +} diff --git a/src/main/java/dev/lions/unionflow/client/service/ComplianceDashboardRestClient.java b/src/main/java/dev/lions/unionflow/client/service/ComplianceDashboardRestClient.java new file mode 100644 index 0000000..98fbe6a --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/service/ComplianceDashboardRestClient.java @@ -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); +} diff --git a/src/main/java/dev/lions/unionflow/client/service/PispiReadinessRestClient.java b/src/main/java/dev/lions/unionflow/client/service/PispiReadinessRestClient.java new file mode 100644 index 0000000..2493957 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/service/PispiReadinessRestClient.java @@ -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(); +} diff --git a/src/main/java/dev/lions/unionflow/client/service/RapportTrimestrielRestClient.java b/src/main/java/dev/lions/unionflow/client/service/RapportTrimestrielRestClient.java new file mode 100644 index 0000000..2b339e5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/service/RapportTrimestrielRestClient.java @@ -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 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); +} diff --git a/src/main/java/dev/lions/unionflow/client/view/ConformiteDashboardBean.java b/src/main/java/dev/lions/unionflow/client/view/ConformiteDashboardBean.java new file mode 100644 index 0000000..0d863ce --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/view/ConformiteDashboardBean.java @@ -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). + * + *

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; } +} diff --git a/src/main/java/dev/lions/unionflow/client/view/PispiReadinessBean.java b/src/main/java/dev/lions/unionflow/client/view/PispiReadinessBean.java new file mode 100644 index 0000000..1c07be5 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/view/PispiReadinessBean.java @@ -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; } +} diff --git a/src/main/java/dev/lions/unionflow/client/view/RapportsTrimestrielsBean.java b/src/main/java/dev/lions/unionflow/client/view/RapportsTrimestrielsBean.java new file mode 100644 index 0000000..011c4c3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/client/view/RapportsTrimestrielsBean.java @@ -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 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 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; } +} diff --git a/src/main/resources/META-INF/resources/pages/secure/admin/pispi-readiness.xhtml b/src/main/resources/META-INF/resources/pages/secure/admin/pispi-readiness.xhtml new file mode 100644 index 0000000..678ba9c --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/admin/pispi-readiness.xhtml @@ -0,0 +1,98 @@ + + + + UnionFlow - PI-SPI Readiness + + +

+
+
+
+
+

PI-SPI Readiness

+

+ Vérification des pré-requis intégration PI-SPI BCEAO avant activation production. +

+
+ +
+ + + + + + +
+
Statut global
+ +
+ Base URL : #{pispiReadinessBean.readiness.baseUrl} +
+
+ + + +
    + +
  • #{b}
  • +
    +
+
+ + + +
    + +
  • #{w}
  • +
    +
+
+ + + + + + + + + + + + + + + + +
+ + +
+ Impossible de récupérer le rapport readiness. +
+
+
+
+
+
+ + diff --git a/src/main/resources/META-INF/resources/pages/secure/conformite/dashboard.xhtml b/src/main/resources/META-INF/resources/pages/secure/conformite/dashboard.xhtml new file mode 100644 index 0000000..d118dec --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/conformite/dashboard.xhtml @@ -0,0 +1,164 @@ + + + + UnionFlow - Tableau de bord conformité + + +
+
+
+
+
+

Tableau de bord conformité

+

+ Indicateurs BCEAO Instr. 001-03-2025 / ARTCI / OHADA / AIRMS +

+
+ +
+ + + + + + +
+
+
+
Score conformité global
+
+ #{conformiteDashboardBean.snapshot.scoreGlobal} +
+
/ 100
+
+
+ +
+
+
Organisation
+
#{conformiteDashboardBean.snapshot.organisationNom}
+
+ Référentiel comptable : #{conformiteDashboardBean.snapshot.referentielComptable} +
+
+
+
+ + +
+
+
+
+ Compliance Officer + +
+
+
+ +
+
+
+ AG annuelle + +
+

#{conformiteDashboardBean.snapshot.agAnnuelle.message}

+
+
+ +
+
+
+ Rapport AIRMS + +
+
+
+ +
+
+
Dirigeants enrôlés CMU
+
#{conformiteDashboardBean.snapshot.dirigeantsAvecCmu}
+
+
+ +
+
+
Taux KYC à jour
+
#{conformiteDashboardBean.snapshot.tauxKycAJourPct} %
+
+
+ +
+
+
Taux formation LBC/FT
+
#{conformiteDashboardBean.snapshot.tauxFormationLbcFtPct} %
+
+
+ +
+
+
Couverture UBO
+
#{conformiteDashboardBean.snapshot.couvertureUboPct} %
+
+
+ +
+
+
Commissaire aux comptes
+ +
+
+ +
+
+
FOMUS-CI
+ +

#{conformiteDashboardBean.snapshot.fomusCi.message}

+
+
+
+ + +
    +
  • + Compliance Officer non désigné — Instruction BCEAO 001-03-2025 obligatoire +
  • +
  • + AG annuelle en retard — échéance 30 juin +
  • +
  • + Score global < 60 % — risque inspection BCEAO/ARTCI +
  • +
+
+
+ + + +
+ Aucune donnée de conformité disponible. +
+
+
+
+
+
+
+
diff --git a/src/main/resources/META-INF/resources/pages/secure/conformite/rapports-trimestriels.xhtml b/src/main/resources/META-INF/resources/pages/secure/conformite/rapports-trimestriels.xhtml new file mode 100644 index 0000000..001b4a4 --- /dev/null +++ b/src/main/resources/META-INF/resources/pages/secure/conformite/rapports-trimestriels.xhtml @@ -0,0 +1,125 @@ + + + + UnionFlow - Rapports trimestriels Contrôleur Interne + + +
+
+
+
+
+

Rapports trimestriels — Contrôleur Interne

+

+ Source AG, inspections BCEAO/ARTCI. Cycle DRAFT → SIGNE → ARCHIVE. +

+
+
+ + + + + + +
+
+ + +
+
+ + + + + + + +
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+
+
diff --git a/src/test/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDtoTest.java b/src/test/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDtoTest.java new file mode 100644 index 0000000..386b3f3 --- /dev/null +++ b/src/test/java/dev/lions/unionflow/client/api/dto/RapportTrimestrielDtoTest.java @@ -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()); + } +} diff --git a/src/test/java/dev/lions/unionflow/client/view/ConformiteDashboardBeanTest.java b/src/test/java/dev/lions/unionflow/client/view/ConformiteDashboardBeanTest.java new file mode 100644 index 0000000..3e7772b --- /dev/null +++ b/src/test/java/dev/lions/unionflow/client/view/ConformiteDashboardBeanTest.java @@ -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()); + } +} diff --git a/src/test/java/dev/lions/unionflow/client/view/PispiReadinessBeanTest.java b/src/test/java/dev/lions/unionflow/client/view/PispiReadinessBeanTest.java new file mode 100644 index 0000000..f4506da --- /dev/null +++ b/src/test/java/dev/lions/unionflow/client/view/PispiReadinessBeanTest.java @@ -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()); + } +}