feat(sprint-11 web 2026-04-25): pages PrimeFaces Sprint 10 (UBO, audit-trail viewer, délégations rôles) + bump api 1.0.6→1.0.8
DRY strict appliqué : web réutilise directement les DTOs officiels de unionflow-server-api 1.0.8 (CreateBeneficiaireEffectifRequest, BeneficiaireEffectifResponse, AuditTrailOperationResponse, CreateRoleDelegationRequest, RoleDelegationResponse) au lieu de DTOs miroirs locaux. Aucune duplication. Bump dépendance api 1.0.6 → 1.0.8 REST clients @RegisterRestClient configKey=unionflow-api - BeneficiaireEffectifRestClient : CRUD lister/trouverParId/creer/mettreAJour/desactiver - AuditTrailRestClient : 5 endpoints lecture (parUtilisateur, historique, parOrganisation, sodViolations, financial) - RoleDelegationRestClient : listerParOrganisation / creer / revoquer Beans @ViewScoped - BeneficiaireEffectifBean : recherche (KYC|org|PEP), création formulaire, marquerPep, désactiver - AuditTrailViewerBean : 5 modes (USER/ENTITY/ORG/SOD_VIOLATIONS/FINANCIAL), couleurAction (DELETE→danger, VALIDATE→success, etc.), couleurSod - RoleDelegationBean : recherche/créer/révoquer, couleurStatut (ACTIVE/REVOQUEE/EXPIREE) Pages XHTML - /pages/secure/conformite/beneficiaires-effectifs.xhtml — recherche + tableau + nouvelle UBO (panel toggleable) - /pages/secure/conformite/audit-trail.xhtml — filtres mode + tableau + détail JSONB (pre format) - /pages/secure/admin/role-delegations.xhtml — table actives + nouvelle (datePicker dates) MenuBean + menu.xhtml - 3 nouveaux flags : isBeneficiairesEffectifsVisible, isAuditTrailViewerVisible, isRoleDelegationsVisible - 3 menuitems ajoutés au sous-menu Conformité existant (icônes pi-users, pi-history, pi-share-alt) - Gating par rôles : COMPLIANCE_OFFICER + CONTROLEUR_INTERNE pour audit ; ADMIN_ORGANISATION + PRESIDENT pour délégations Tests (10/10 verts, 31/31 cumulé S8+S11) - AuditTrailViewerBeanTest : 8 tests (couleurAction × 6 cas, couleurSod, defaults) - RoleDelegationBeanTest : 2 tests (couleurStatut × 5, defaults)
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -142,7 +142,7 @@
|
||||
<dependency>
|
||||
<groupId>dev.lions.unionflow</groupId>
|
||||
<artifactId>unionflow-server-api</artifactId>
|
||||
<version>1.0.6</version>
|
||||
<version>1.0.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Lions User Manager Client - Module réutilisable de gestion d'utilisateurs Keycloak -->
|
||||
|
||||
@@ -646,6 +646,31 @@ public class MenuBean implements Serializable {
|
||||
return hasAnyRole("SUPER_ADMIN", "COMPLIANCE_OFFICER");
|
||||
}
|
||||
|
||||
/**
|
||||
* Bénéficiaires Effectifs (UBO) — Instr. BCEAO 003-03-2025.
|
||||
* @since 2026-04-25 (Sprint 11)
|
||||
*/
|
||||
public boolean isBeneficiairesEffectifsVisible() {
|
||||
return hasAnyRole("SUPER_ADMIN", "ADMIN_ORGANISATION", "COMPLIANCE_OFFICER",
|
||||
"CONTROLEUR_INTERNE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit Trail viewer — compliance / contrôle interne.
|
||||
* @since 2026-04-25 (Sprint 11)
|
||||
*/
|
||||
public boolean isAuditTrailViewerVisible() {
|
||||
return hasAnyRole("SUPER_ADMIN", "COMPLIANCE_OFFICER", "CONTROLEUR_INTERNE");
|
||||
}
|
||||
|
||||
/**
|
||||
* Délégations de rôles — admin org / président.
|
||||
* @since 2026-04-25 (Sprint 11)
|
||||
*/
|
||||
public boolean isRoleDelegationsVisible() {
|
||||
return hasAnyRole("SUPER_ADMIN", "ADMIN_ORGANISATION", "PRESIDENT");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne true si l'organisation active dispose d'au moins un module métier spécifique
|
||||
* (au-delà des modules communs toujours disponibles).
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import dev.lions.unionflow.server.api.dto.audit.response.AuditTrailOperationResponse;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Client REST de lecture audit trail (Sprint 11 ⇄ backend Sprint 10 CQRS read).
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/audit-trail")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface AuditTrailRestClient {
|
||||
|
||||
@GET
|
||||
@Path("/by-user/{userId}")
|
||||
List<AuditTrailOperationResponse> parUtilisateur(
|
||||
@PathParam("userId") UUID userId,
|
||||
@QueryParam("from") String from,
|
||||
@QueryParam("to") String to);
|
||||
|
||||
@GET
|
||||
@Path("/by-entity/{type}/{id}")
|
||||
List<AuditTrailOperationResponse> historique(
|
||||
@PathParam("type") String entityType, @PathParam("id") UUID entityId);
|
||||
|
||||
@GET
|
||||
@Path("/by-organisation/{orgId}")
|
||||
List<AuditTrailOperationResponse> parOrganisation(@PathParam("orgId") UUID orgId);
|
||||
|
||||
@GET
|
||||
@Path("/sod-violations")
|
||||
List<AuditTrailOperationResponse> violationsSod();
|
||||
|
||||
@GET
|
||||
@Path("/financial/{orgId}")
|
||||
List<AuditTrailOperationResponse> operationsFinancieres(
|
||||
@PathParam("orgId") UUID orgId,
|
||||
@QueryParam("from") String from,
|
||||
@QueryParam("to") String to);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.request.CreateBeneficiaireEffectifRequest;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.request.UpdateBeneficiaireEffectifRequest;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.response.BeneficiaireEffectifResponse;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Client REST des Bénéficiaires Effectifs (Sprint 11 ⇄ backend Sprint 10).
|
||||
* Réutilise les DTOs officiels de unionflow-server-api 1.0.8 — zéro duplication.
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/kyc/beneficiaires-effectifs")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface BeneficiaireEffectifRestClient {
|
||||
|
||||
@GET
|
||||
List<BeneficiaireEffectifResponse> lister(
|
||||
@QueryParam("kycDossierId") UUID kycDossierId,
|
||||
@QueryParam("organisationCibleId") UUID organisationCibleId,
|
||||
@QueryParam("pep") Boolean pep);
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
BeneficiaireEffectifResponse trouverParId(@PathParam("id") UUID id);
|
||||
|
||||
@POST
|
||||
BeneficiaireEffectifResponse creer(CreateBeneficiaireEffectifRequest request);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
BeneficiaireEffectifResponse mettreAJour(
|
||||
@PathParam("id") UUID id, UpdateBeneficiaireEffectifRequest request);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void desactiver(@PathParam("id") UUID id);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package dev.lions.unionflow.client.service;
|
||||
|
||||
import dev.lions.unionflow.client.security.AuthHeaderFactory;
|
||||
import dev.lions.unionflow.server.api.dto.delegation.request.CreateRoleDelegationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.delegation.response.RoleDelegationResponse;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Client REST des délégations de rôle (Sprint 11 ⇄ backend Sprint 10).
|
||||
*/
|
||||
@RegisterRestClient(configKey = "unionflow-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/role-delegations")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface RoleDelegationRestClient {
|
||||
|
||||
@GET
|
||||
@Path("/organisation/{orgId}")
|
||||
List<RoleDelegationResponse> listerParOrganisation(@PathParam("orgId") UUID orgId);
|
||||
|
||||
@POST
|
||||
RoleDelegationResponse creer(
|
||||
CreateRoleDelegationRequest request,
|
||||
@QueryParam("rolesDelegataire") String rolesDelegataireCsv);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
RoleDelegationResponse revoquer(@PathParam("id") UUID id);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.service.AuditTrailRestClient;
|
||||
import dev.lions.unionflow.server.api.dto.audit.response.AuditTrailOperationResponse;
|
||||
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 java.time.LocalDateTime;
|
||||
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 viewer audit trail (Sprint 11 ⇄ backend Sprint 10 CQRS read).
|
||||
* Pas d'écriture — la production des événements est dans le backend lifecycle.
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class AuditTrailViewerBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(AuditTrailViewerBean.class);
|
||||
|
||||
@Inject @RestClient AuditTrailRestClient client;
|
||||
|
||||
/** Mode : USER, ENTITY, ORG, SOD_VIOLATIONS, FINANCIAL */
|
||||
private String mode = "ORG";
|
||||
|
||||
private UUID userId;
|
||||
private UUID orgId;
|
||||
private String entityType;
|
||||
private UUID entityId;
|
||||
private LocalDateTime from = LocalDateTime.now().minusDays(30);
|
||||
private LocalDateTime to = LocalDateTime.now();
|
||||
|
||||
private List<AuditTrailOperationResponse> operations = Collections.emptyList();
|
||||
private AuditTrailOperationResponse selection;
|
||||
private String erreur;
|
||||
|
||||
public void rechercher() {
|
||||
erreur = null;
|
||||
try {
|
||||
operations = switch (mode) {
|
||||
case "USER" -> userId != null
|
||||
? client.parUtilisateur(userId, from.toString(), to.toString())
|
||||
: Collections.emptyList();
|
||||
case "ENTITY" -> entityType != null && entityId != null
|
||||
? client.historique(entityType, entityId)
|
||||
: Collections.emptyList();
|
||||
case "ORG" -> orgId != null
|
||||
? client.parOrganisation(orgId)
|
||||
: Collections.emptyList();
|
||||
case "SOD_VIOLATIONS" -> client.violationsSod();
|
||||
case "FINANCIAL" -> orgId != null
|
||||
? client.operationsFinancieres(orgId, from.toString(), to.toString())
|
||||
: Collections.emptyList();
|
||||
default -> Collections.emptyList();
|
||||
};
|
||||
LOG.infof("Audit trail (%s) chargé : %d entrées", mode, operations.size());
|
||||
} catch (Exception e) {
|
||||
handleError("Recherche audit trail échouée", e);
|
||||
operations = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public String getCouleurAction(String actionType) {
|
||||
if (actionType == null) return "secondary";
|
||||
return switch (actionType) {
|
||||
case "DELETE", "PAYMENT_FAILED" -> "danger";
|
||||
case "VALIDATE", "PAYMENT_CONFIRMED", "AID_REQUEST_APPROVED" -> "success";
|
||||
case "UPDATE", "PAYMENT_INITIATED", "BUDGET_APPROVED" -> "info";
|
||||
case "CREATE" -> "primary";
|
||||
case "EXPORT" -> "warning";
|
||||
default -> "secondary";
|
||||
};
|
||||
}
|
||||
|
||||
public String getCouleurSod(Boolean sodCheckPassed) {
|
||||
if (sodCheckPassed == null) return "secondary";
|
||||
return sodCheckPassed ? "success" : "danger";
|
||||
}
|
||||
|
||||
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 String getMode() { return mode; }
|
||||
public void setMode(String mode) { this.mode = mode; }
|
||||
public UUID getUserId() { return userId; }
|
||||
public void setUserId(UUID userId) { this.userId = userId; }
|
||||
public UUID getOrgId() { return orgId; }
|
||||
public void setOrgId(UUID orgId) { this.orgId = orgId; }
|
||||
public String getEntityType() { return entityType; }
|
||||
public void setEntityType(String entityType) { this.entityType = entityType; }
|
||||
public UUID getEntityId() { return entityId; }
|
||||
public void setEntityId(UUID entityId) { this.entityId = entityId; }
|
||||
public LocalDateTime getFrom() { return from; }
|
||||
public void setFrom(LocalDateTime from) { this.from = from; }
|
||||
public LocalDateTime getTo() { return to; }
|
||||
public void setTo(LocalDateTime to) { this.to = to; }
|
||||
public List<AuditTrailOperationResponse> getOperations() { return operations; }
|
||||
public AuditTrailOperationResponse getSelection() { return selection; }
|
||||
public void setSelection(AuditTrailOperationResponse selection) { this.selection = selection; }
|
||||
public String getErreur() { return erreur; }
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.service.BeneficiaireEffectifRestClient;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.request.CreateBeneficiaireEffectifRequest;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.request.UpdateBeneficiaireEffectifRequest;
|
||||
import dev.lions.unionflow.server.api.dto.kyc.response.BeneficiaireEffectifResponse;
|
||||
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 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 CRUD des Bénéficiaires Effectifs (Sprint 11 ⇄ backend Sprint 10).
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class BeneficiaireEffectifBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(BeneficiaireEffectifBean.class);
|
||||
|
||||
@Inject @RestClient BeneficiaireEffectifRestClient client;
|
||||
|
||||
// Filtres
|
||||
private UUID kycDossierId;
|
||||
private UUID organisationCibleId;
|
||||
private Boolean filtrePep;
|
||||
|
||||
private List<BeneficiaireEffectifResponse> ubos = Collections.emptyList();
|
||||
private BeneficiaireEffectifResponse selection;
|
||||
private String erreur;
|
||||
|
||||
// Formulaire de création
|
||||
private String nom;
|
||||
private String prenoms;
|
||||
private String nationalite;
|
||||
private String paysResidence;
|
||||
private String numeroPieceIdentite;
|
||||
private String pourcentageCapital;
|
||||
private String natureControle = "DETENTION_CAPITAL";
|
||||
private boolean estPep;
|
||||
|
||||
public void rechercher() {
|
||||
erreur = null;
|
||||
try {
|
||||
ubos = client.lister(kycDossierId, organisationCibleId, filtrePep);
|
||||
LOG.infof("UBOs chargés : %d", ubos.size());
|
||||
} catch (Exception e) {
|
||||
handleError("Recherche UBO échouée", e);
|
||||
ubos = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public void creer() {
|
||||
if (kycDossierId == null && organisationCibleId == null) {
|
||||
addMessage(FacesMessage.SEVERITY_WARN, "Cible requise",
|
||||
"Sélectionnez d'abord un KycDossier ou une organisation cible");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CreateBeneficiaireEffectifRequest req = CreateBeneficiaireEffectifRequest.builder()
|
||||
.kycDossierId(kycDossierId)
|
||||
.organisationCibleId(organisationCibleId)
|
||||
.nom(nom)
|
||||
.prenoms(prenoms)
|
||||
.nationalite(nationalite != null ? nationalite.toUpperCase() : null)
|
||||
.paysResidence(paysResidence != null ? paysResidence.toUpperCase() : null)
|
||||
.numeroPieceIdentite(numeroPieceIdentite)
|
||||
.pourcentageCapital(pourcentageCapital != null && !pourcentageCapital.isBlank()
|
||||
? new java.math.BigDecimal(pourcentageCapital) : null)
|
||||
.natureControle(natureControle)
|
||||
.estPep(estPep)
|
||||
.build();
|
||||
BeneficiaireEffectifResponse created = client.creer(req);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "UBO créé",
|
||||
created.prenoms() + " " + created.nom());
|
||||
resetForm();
|
||||
rechercher();
|
||||
} catch (Exception e) {
|
||||
handleError("Création UBO échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void desactiverSelection() {
|
||||
if (selection == null) return;
|
||||
try {
|
||||
client.desactiver(selection.id());
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "UBO désactivé",
|
||||
selection.prenoms() + " " + selection.nom());
|
||||
rechercher();
|
||||
} catch (Exception e) {
|
||||
handleError("Désactivation échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void marquerPepSelection() {
|
||||
if (selection == null) return;
|
||||
try {
|
||||
UpdateBeneficiaireEffectifRequest req = UpdateBeneficiaireEffectifRequest.builder()
|
||||
.estPep(true).build();
|
||||
client.mettreAJour(selection.id(), req);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "UBO marqué PEP", "");
|
||||
rechercher();
|
||||
} catch (Exception e) {
|
||||
handleError("Mise à jour PEP échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetForm() {
|
||||
nom = null;
|
||||
prenoms = null;
|
||||
nationalite = null;
|
||||
paysResidence = null;
|
||||
numeroPieceIdentite = null;
|
||||
pourcentageCapital = null;
|
||||
natureControle = "DETENTION_CAPITAL";
|
||||
estPep = false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
public UUID getKycDossierId() { return kycDossierId; }
|
||||
public void setKycDossierId(UUID kycDossierId) { this.kycDossierId = kycDossierId; }
|
||||
public UUID getOrganisationCibleId() { return organisationCibleId; }
|
||||
public void setOrganisationCibleId(UUID id) { this.organisationCibleId = id; }
|
||||
public Boolean getFiltrePep() { return filtrePep; }
|
||||
public void setFiltrePep(Boolean filtrePep) { this.filtrePep = filtrePep; }
|
||||
public List<BeneficiaireEffectifResponse> getUbos() { return ubos; }
|
||||
public BeneficiaireEffectifResponse getSelection() { return selection; }
|
||||
public void setSelection(BeneficiaireEffectifResponse selection) { this.selection = selection; }
|
||||
public String getErreur() { return erreur; }
|
||||
public String getNom() { return nom; }
|
||||
public void setNom(String nom) { this.nom = nom; }
|
||||
public String getPrenoms() { return prenoms; }
|
||||
public void setPrenoms(String prenoms) { this.prenoms = prenoms; }
|
||||
public String getNationalite() { return nationalite; }
|
||||
public void setNationalite(String nationalite) { this.nationalite = nationalite; }
|
||||
public String getPaysResidence() { return paysResidence; }
|
||||
public void setPaysResidence(String paysResidence) { this.paysResidence = paysResidence; }
|
||||
public String getNumeroPieceIdentite() { return numeroPieceIdentite; }
|
||||
public void setNumeroPieceIdentite(String n) { this.numeroPieceIdentite = n; }
|
||||
public String getPourcentageCapital() { return pourcentageCapital; }
|
||||
public void setPourcentageCapital(String pct) { this.pourcentageCapital = pct; }
|
||||
public String getNatureControle() { return natureControle; }
|
||||
public void setNatureControle(String nc) { this.natureControle = nc; }
|
||||
public boolean isEstPep() { return estPep; }
|
||||
public void setEstPep(boolean estPep) { this.estPep = estPep; }
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import dev.lions.unionflow.client.service.RoleDelegationRestClient;
|
||||
import dev.lions.unionflow.server.api.dto.delegation.request.CreateRoleDelegationRequest;
|
||||
import dev.lions.unionflow.server.api.dto.delegation.response.RoleDelegationResponse;
|
||||
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 java.time.LocalDateTime;
|
||||
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/création/révocation des délégations de rôle (Sprint 11 ⇄ backend Sprint 10).
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class RoleDelegationBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOG = Logger.getLogger(RoleDelegationBean.class);
|
||||
|
||||
@Inject @RestClient RoleDelegationRestClient client;
|
||||
|
||||
private UUID organisationId;
|
||||
private List<RoleDelegationResponse> delegations = Collections.emptyList();
|
||||
private RoleDelegationResponse selection;
|
||||
private String erreur;
|
||||
|
||||
// Formulaire création
|
||||
private UUID delegantUserId;
|
||||
private UUID delegataireUserId;
|
||||
private String roleDelegue = "TRESORIER";
|
||||
private LocalDateTime dateDebut = LocalDateTime.now().plusHours(1);
|
||||
private LocalDateTime dateFin = LocalDateTime.now().plusDays(14);
|
||||
private String motif;
|
||||
private String rolesDelegataireCsv;
|
||||
|
||||
public void rechercher() {
|
||||
erreur = null;
|
||||
if (organisationId == null) return;
|
||||
try {
|
||||
delegations = client.listerParOrganisation(organisationId);
|
||||
LOG.infof("Délégations chargées org=%s : %d", organisationId, delegations.size());
|
||||
} catch (Exception e) {
|
||||
handleError("Chargement délégations échoué", e);
|
||||
delegations = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public void creer() {
|
||||
if (organisationId == null || delegantUserId == null || delegataireUserId == null) {
|
||||
addMessage(FacesMessage.SEVERITY_WARN, "Champs requis",
|
||||
"Organisation, déléguant et délégataire sont obligatoires");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CreateRoleDelegationRequest req = CreateRoleDelegationRequest.builder()
|
||||
.organisationId(organisationId)
|
||||
.delegantUserId(delegantUserId)
|
||||
.delegataireUserId(delegataireUserId)
|
||||
.roleDelegue(roleDelegue)
|
||||
.dateDebut(dateDebut)
|
||||
.dateFin(dateFin)
|
||||
.motif(motif)
|
||||
.build();
|
||||
RoleDelegationResponse created = client.creer(req, rolesDelegataireCsv);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Délégation créée",
|
||||
"Rôle " + created.roleDelegue() + " jusqu'au " + created.dateFin());
|
||||
resetForm();
|
||||
rechercher();
|
||||
} catch (Exception e) {
|
||||
handleError("Création délégation échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void revoquerSelection() {
|
||||
if (selection == null) return;
|
||||
try {
|
||||
client.revoquer(selection.id());
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Délégation révoquée",
|
||||
"Rôle " + selection.roleDelegue());
|
||||
rechercher();
|
||||
} catch (Exception e) {
|
||||
handleError("Révocation échouée", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCouleurStatut(String statut) {
|
||||
if (statut == null) return "secondary";
|
||||
return switch (statut) {
|
||||
case "ACTIVE" -> "success";
|
||||
case "REVOQUEE" -> "danger";
|
||||
case "EXPIREE" -> "warning";
|
||||
default -> "secondary";
|
||||
};
|
||||
}
|
||||
|
||||
private void resetForm() {
|
||||
delegantUserId = null;
|
||||
delegataireUserId = null;
|
||||
roleDelegue = "TRESORIER";
|
||||
dateDebut = LocalDateTime.now().plusHours(1);
|
||||
dateFin = LocalDateTime.now().plusDays(14);
|
||||
motif = null;
|
||||
rolesDelegataireCsv = null;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
public UUID getOrganisationId() { return organisationId; }
|
||||
public void setOrganisationId(UUID organisationId) { this.organisationId = organisationId; }
|
||||
public List<RoleDelegationResponse> getDelegations() { return delegations; }
|
||||
public RoleDelegationResponse getSelection() { return selection; }
|
||||
public void setSelection(RoleDelegationResponse selection) { this.selection = selection; }
|
||||
public String getErreur() { return erreur; }
|
||||
public UUID getDelegantUserId() { return delegantUserId; }
|
||||
public void setDelegantUserId(UUID id) { this.delegantUserId = id; }
|
||||
public UUID getDelegataireUserId() { return delegataireUserId; }
|
||||
public void setDelegataireUserId(UUID id) { this.delegataireUserId = id; }
|
||||
public String getRoleDelegue() { return roleDelegue; }
|
||||
public void setRoleDelegue(String roleDelegue) { this.roleDelegue = roleDelegue; }
|
||||
public LocalDateTime getDateDebut() { return dateDebut; }
|
||||
public void setDateDebut(LocalDateTime dateDebut) { this.dateDebut = dateDebut; }
|
||||
public LocalDateTime getDateFin() { return dateFin; }
|
||||
public void setDateFin(LocalDateTime dateFin) { this.dateFin = dateFin; }
|
||||
public String getMotif() { return motif; }
|
||||
public void setMotif(String motif) { this.motif = motif; }
|
||||
public String getRolesDelegataireCsv() { return rolesDelegataireCsv; }
|
||||
public void setRolesDelegataireCsv(String csv) { this.rolesDelegataireCsv = csv; }
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<!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 - Délégations de rôles</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h2 class="text-900 font-medium text-3xl m-0">Délégations temporaires de rôles</h2>
|
||||
<p class="text-600 mt-1 mb-3">
|
||||
Délégation d'un rôle pour absence prolongée. Vérification SoD à la création.
|
||||
</p>
|
||||
|
||||
<h:form id="form-deleg">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<p:panel header="Recherche" styleClass="mb-3">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-9">
|
||||
<p:outputLabel for="org" value="Organisation (UUID)" />
|
||||
<p:inputText id="org" value="#{roleDelegationBean.organisationId}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-3 flex align-items-end">
|
||||
<p:commandButton value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
action="#{roleDelegationBean.rechercher}"
|
||||
update="form-deleg"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
<p:dataTable value="#{roleDelegationBean.delegations}"
|
||||
var="d"
|
||||
selection="#{roleDelegationBean.selection}"
|
||||
rowKey="#{d.id}"
|
||||
selectionMode="single"
|
||||
emptyMessage="Aucune délégation">
|
||||
<p:column headerText="Rôle délégué">
|
||||
<h:outputText value="#{d.roleDelegue}" />
|
||||
</p:column>
|
||||
<p:column headerText="Déléguant">
|
||||
<h:outputText value="#{d.delegantUserId}" />
|
||||
</p:column>
|
||||
<p:column headerText="Délégataire">
|
||||
<h:outputText value="#{d.delegataireUserId}" />
|
||||
</p:column>
|
||||
<p:column headerText="Période">
|
||||
<h:outputText value="du #{d.dateDebut} au #{d.dateFin}" />
|
||||
</p:column>
|
||||
<p:column headerText="Motif">
|
||||
<h:outputText value="#{d.motif}" />
|
||||
</p:column>
|
||||
<p:column headerText="Statut">
|
||||
<p:tag value="#{d.statut}"
|
||||
severity="#{roleDelegationBean.getCouleurStatut(d.statut)}" />
|
||||
</p:column>
|
||||
<p:column headerText="Active">
|
||||
<p:tag value="#{d.estActive ? 'OUI' : 'NON'}"
|
||||
severity="#{d.estActive ? 'success' : 'secondary'}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
|
||||
<div class="flex gap-2 mt-3">
|
||||
<p:commandButton value="Révoquer (sélection)"
|
||||
icon="pi pi-times"
|
||||
action="#{roleDelegationBean.revoquerSelection}"
|
||||
update="form-deleg"
|
||||
styleClass="p-button-danger" />
|
||||
</div>
|
||||
|
||||
<p:panel header="Nouvelle délégation" toggleable="true" collapsed="true" styleClass="mt-4">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<p:outputLabel for="dgt" value="Déléguant (UUID utilisateur) *" />
|
||||
<p:inputText id="dgt" value="#{roleDelegationBean.delegantUserId}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:outputLabel for="dgtr" value="Délégataire (UUID utilisateur) *" />
|
||||
<p:inputText id="dgtr" value="#{roleDelegationBean.delegataireUserId}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="role" value="Rôle délégué *" />
|
||||
<p:selectOneMenu id="role" value="#{roleDelegationBean.roleDelegue}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemValue="TRESORIER" itemLabel="TRESORIER" />
|
||||
<f:selectItem itemValue="SECRETAIRE" itemLabel="SECRETAIRE" />
|
||||
<f:selectItem itemValue="ADMIN_ORGANISATION" itemLabel="ADMIN_ORGANISATION" />
|
||||
<f:selectItem itemValue="COMPLIANCE_OFFICER" itemLabel="COMPLIANCE_OFFICER" />
|
||||
<f:selectItem itemValue="CONTROLEUR_INTERNE" itemLabel="CONTROLEUR_INTERNE" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="rolesDeleg" value="Rôles existants délégataire (CSV)" />
|
||||
<p:inputText id="rolesDeleg" value="#{roleDelegationBean.rolesDelegataireCsv}"
|
||||
styleClass="w-full" placeholder="MEMBRE_ACTIF,MEMBRE_BUREAU" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="motif" value="Motif" />
|
||||
<p:inputText id="motif" value="#{roleDelegationBean.motif}"
|
||||
styleClass="w-full" placeholder="Congé, mission..." />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:outputLabel for="dd" value="Date début *" />
|
||||
<p:datePicker id="dd" value="#{roleDelegationBean.dateDebut}"
|
||||
showTime="true" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:outputLabel for="df" value="Date fin *" />
|
||||
<p:datePicker id="df" value="#{roleDelegationBean.dateFin}"
|
||||
showTime="true" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p:commandButton value="Créer délégation"
|
||||
icon="pi pi-plus"
|
||||
action="#{roleDelegationBean.creer}"
|
||||
update="form-deleg" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!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 - Audit Trail</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h2 class="text-900 font-medium text-3xl m-0">Audit Trail</h2>
|
||||
<p class="text-600 mt-1 mb-3">
|
||||
Historique enrichi des opérations sensibles + détection violations SoD.
|
||||
</p>
|
||||
|
||||
<h:form id="form-audit">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<p:panel header="Filtres" styleClass="mb-3">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-3">
|
||||
<p:outputLabel for="mode" value="Mode" />
|
||||
<p:selectOneMenu id="mode" value="#{auditTrailViewerBean.mode}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemValue="ORG" itemLabel="Par organisation" />
|
||||
<f:selectItem itemValue="USER" itemLabel="Par utilisateur" />
|
||||
<f:selectItem itemValue="ENTITY" itemLabel="Historique entité" />
|
||||
<f:selectItem itemValue="SOD_VIOLATIONS" itemLabel="Violations SoD" />
|
||||
<f:selectItem itemValue="FINANCIAL" itemLabel="Opérations financières" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<p:outputLabel for="org" value="Organisation (UUID)" />
|
||||
<p:inputText id="org" value="#{auditTrailViewerBean.orgId}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<p:outputLabel for="usr" value="Utilisateur (UUID)" />
|
||||
<p:inputText id="usr" value="#{auditTrailViewerBean.userId}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-3 flex align-items-end">
|
||||
<p:commandButton value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
action="#{auditTrailViewerBean.rechercher}"
|
||||
update="form-audit"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
<p:dataTable value="#{auditTrailViewerBean.operations}"
|
||||
var="o"
|
||||
selection="#{auditTrailViewerBean.selection}"
|
||||
rowKey="#{o.id}"
|
||||
selectionMode="single"
|
||||
emptyMessage="Aucune opération trouvée">
|
||||
<p:column headerText="Date">
|
||||
<h:outputText value="#{o.operationAt}" />
|
||||
</p:column>
|
||||
<p:column headerText="Utilisateur">
|
||||
<h:outputText value="#{o.userEmail}" />
|
||||
<h:outputText value=" (#{o.roleActif})" rendered="#{o.roleActif != null}"
|
||||
styleClass="text-600 text-sm" />
|
||||
</p:column>
|
||||
<p:column headerText="Action">
|
||||
<p:tag value="#{o.actionType}"
|
||||
severity="#{auditTrailViewerBean.getCouleurAction(o.actionType)}" />
|
||||
</p:column>
|
||||
<p:column headerText="Entité">
|
||||
<h:outputText value="#{o.entityType}" />
|
||||
</p:column>
|
||||
<p:column headerText="Description">
|
||||
<h:outputText value="#{o.description}" />
|
||||
</p:column>
|
||||
<p:column headerText="SoD">
|
||||
<p:tag value="#{o.sodCheckPassed ? 'OK' : 'VIOLATION'}"
|
||||
severity="#{auditTrailViewerBean.getCouleurSod(o.sodCheckPassed)}"
|
||||
rendered="#{o.sodCheckPassed != null}" />
|
||||
<h:outputText value="—" rendered="#{o.sodCheckPassed == null}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
|
||||
<p:panel header="Détail opération" rendered="#{auditTrailViewerBean.selection != null}"
|
||||
styleClass="mt-3">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<strong>SoD violations :</strong>
|
||||
<h:outputText value="#{auditTrailViewerBean.selection.sodViolations}" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<strong>Payload avant :</strong>
|
||||
<pre style="font-size: 0.85em; background:#f5f5f5; padding:8px;">#{auditTrailViewerBean.selection.payloadAvant}</pre>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<strong>Payload après :</strong>
|
||||
<pre style="font-size: 0.85em; background:#f5f5f5; padding:8px;">#{auditTrailViewerBean.selection.payloadApres}</pre>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<strong>Métadonnées :</strong>
|
||||
<pre style="font-size: 0.85em; background:#f5f5f5; padding:8px;">#{auditTrailViewerBean.selection.metadata}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,147 @@
|
||||
<!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 - Bénéficiaires Effectifs (UBO)</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h2 class="text-900 font-medium text-3xl m-0">Bénéficiaires Effectifs (UBO)</h2>
|
||||
<p class="text-600 mt-1 mb-3">
|
||||
Instr. BCEAO 003-03-2025 — identification des personnes physiques contrôlant l'organisation.
|
||||
</p>
|
||||
|
||||
<h:form id="form-ubo">
|
||||
<p:messages closable="true" />
|
||||
|
||||
<p:panel header="Recherche" styleClass="mb-3">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="kyc" value="KycDossier ID (UUID)" />
|
||||
<p:inputText id="kyc" value="#{beneficiaireEffectifBean.kycDossierId}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="org" value="Organisation cible (UUID)" />
|
||||
<p:inputText id="org" value="#{beneficiaireEffectifBean.organisationCibleId}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-2 flex align-items-end">
|
||||
<p:selectBooleanCheckbox value="#{beneficiaireEffectifBean.filtrePep}"
|
||||
itemLabel="PEP uniquement" />
|
||||
</div>
|
||||
<div class="col-12 md:col-2 flex align-items-end">
|
||||
<p:commandButton value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
action="#{beneficiaireEffectifBean.rechercher}"
|
||||
update="form-ubo"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
<p:dataTable value="#{beneficiaireEffectifBean.ubos}"
|
||||
var="u"
|
||||
selection="#{beneficiaireEffectifBean.selection}"
|
||||
rowKey="#{u.id}"
|
||||
selectionMode="single"
|
||||
emptyMessage="Aucun UBO trouvé">
|
||||
<p:column headerText="Nom complet">
|
||||
#{u.prenoms} #{u.nom}
|
||||
</p:column>
|
||||
<p:column headerText="Nationalité">
|
||||
<h:outputText value="#{u.nationalite}" />
|
||||
</p:column>
|
||||
<p:column headerText="% Capital">
|
||||
<h:outputText value="#{u.pourcentageCapital}" />
|
||||
</p:column>
|
||||
<p:column headerText="Nature contrôle">
|
||||
<h:outputText value="#{u.natureControle}" />
|
||||
</p:column>
|
||||
<p:column headerText="PEP">
|
||||
<p:tag value="PEP" severity="warning" rendered="#{u.estPep}" />
|
||||
<h:outputText value="—" rendered="#{not u.estPep}" />
|
||||
</p:column>
|
||||
<p:column headerText="Sanctions">
|
||||
<p:tag value="LISTÉ" severity="danger" rendered="#{u.presenceListesSanctions}" />
|
||||
<h:outputText value="—" rendered="#{not u.presenceListesSanctions}" />
|
||||
</p:column>
|
||||
<p:column headerText="Statut">
|
||||
<p:tag value="#{u.actif ? 'ACTIF' : 'INACTIF'}"
|
||||
severity="#{u.actif ? 'success' : 'secondary'}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
|
||||
<div class="flex gap-2 mt-3">
|
||||
<p:commandButton value="Marquer PEP"
|
||||
icon="pi pi-flag"
|
||||
action="#{beneficiaireEffectifBean.marquerPepSelection}"
|
||||
update="form-ubo"
|
||||
styleClass="p-button-warning" />
|
||||
<p:commandButton value="Désactiver"
|
||||
icon="pi pi-times"
|
||||
action="#{beneficiaireEffectifBean.desactiverSelection}"
|
||||
update="form-ubo"
|
||||
styleClass="p-button-secondary" />
|
||||
</div>
|
||||
|
||||
<p:panel header="Nouveau UBO" toggleable="true" collapsed="true" styleClass="mt-4">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="nom" value="Nom *" />
|
||||
<p:inputText id="nom" value="#{beneficiaireEffectifBean.nom}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="prenoms" value="Prénoms *" />
|
||||
<p:inputText id="prenoms" value="#{beneficiaireEffectifBean.prenoms}" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="nat" value="Nationalité (ISO-3) *" />
|
||||
<p:inputText id="nat" value="#{beneficiaireEffectifBean.nationalite}"
|
||||
maxlength="3" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="pays" value="Pays résidence (ISO-3)" />
|
||||
<p:inputText id="pays" value="#{beneficiaireEffectifBean.paysResidence}"
|
||||
maxlength="3" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="pct" value="% Capital (0-100)" />
|
||||
<p:inputText id="pct" value="#{beneficiaireEffectifBean.pourcentageCapital}"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<p:outputLabel for="nat-ctl" value="Nature contrôle *" />
|
||||
<p:selectOneMenu id="nat-ctl" value="#{beneficiaireEffectifBean.natureControle}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemValue="DETENTION_CAPITAL" itemLabel="Détention capital" />
|
||||
<f:selectItem itemValue="DROITS_VOTE" itemLabel="Droits de vote" />
|
||||
<f:selectItem itemValue="CONTROLE_DE_FAIT" itemLabel="Contrôle de fait" />
|
||||
<f:selectItem itemValue="BENEFICIAIRE_ULTIME" itemLabel="Bénéficiaire ultime" />
|
||||
<f:selectItem itemValue="MANDAT_REPRESENTATION" itemLabel="Mandat" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p:selectBooleanCheckbox value="#{beneficiaireEffectifBean.estPep}"
|
||||
itemLabel="Personne Politiquement Exposée (PEP)" />
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p:commandButton value="Créer UBO"
|
||||
icon="pi pi-plus"
|
||||
action="#{beneficiaireEffectifBean.creer}"
|
||||
update="form-ubo" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -113,6 +113,9 @@
|
||||
<p:submenu id="m_conformite" label="Conformité" icon="pi pi-verified" rendered="#{menuBean.conformiteDashboardVisible}">
|
||||
<p:menuitem id="m_conformite_dashboard" value="Tableau de bord" icon="pi pi-chart-bar" outcome="/pages/secure/conformite/dashboard" />
|
||||
<p:menuitem id="m_rapports_trimestriels" value="Rapports trimestriels" icon="pi pi-file-pdf" outcome="/pages/secure/conformite/rapports-trimestriels" rendered="#{menuBean.rapportsTrimestrielsVisible}" />
|
||||
<p:menuitem id="m_ubo" value="Bénéficiaires Effectifs" icon="pi pi-users" outcome="/pages/secure/conformite/beneficiaires-effectifs" rendered="#{menuBean.beneficiairesEffectifsVisible}" />
|
||||
<p:menuitem id="m_audit_trail" value="Audit Trail" icon="pi pi-history" outcome="/pages/secure/conformite/audit-trail" rendered="#{menuBean.auditTrailViewerVisible}" />
|
||||
<p:menuitem id="m_role_delegations" value="Délégations de rôles" icon="pi pi-share-alt" outcome="/pages/secure/admin/role-delegations" rendered="#{menuBean.roleDelegationsVisible}" />
|
||||
<p:menuitem id="m_pispi_readiness" value="PI-SPI Readiness" icon="pi pi-cog" outcome="/pages/secure/admin/pispi-readiness" rendered="#{menuBean.pispiReadinessVisible}" />
|
||||
</p:submenu>
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class AuditTrailViewerBeanTest {
|
||||
|
||||
private AuditTrailViewerBean bean;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bean = new AuditTrailViewerBean();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — DELETE → danger")
|
||||
void couleurDelete() {
|
||||
assertEquals("danger", bean.getCouleurAction("DELETE"));
|
||||
assertEquals("danger", bean.getCouleurAction("PAYMENT_FAILED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — VALIDATE / payment confirmé / aide approuvée → success")
|
||||
void couleurSuccess() {
|
||||
assertEquals("success", bean.getCouleurAction("VALIDATE"));
|
||||
assertEquals("success", bean.getCouleurAction("PAYMENT_CONFIRMED"));
|
||||
assertEquals("success", bean.getCouleurAction("AID_REQUEST_APPROVED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — UPDATE / payment initié / budget approuvé → info")
|
||||
void couleurInfo() {
|
||||
assertEquals("info", bean.getCouleurAction("UPDATE"));
|
||||
assertEquals("info", bean.getCouleurAction("PAYMENT_INITIATED"));
|
||||
assertEquals("info", bean.getCouleurAction("BUDGET_APPROVED"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — CREATE → primary")
|
||||
void couleurCreate() {
|
||||
assertEquals("primary", bean.getCouleurAction("CREATE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — EXPORT → warning")
|
||||
void couleurExport() {
|
||||
assertEquals("warning", bean.getCouleurAction("EXPORT"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurAction — null/inconnu → secondary")
|
||||
void couleurDefault() {
|
||||
assertEquals("secondary", bean.getCouleurAction(null));
|
||||
assertEquals("secondary", bean.getCouleurAction("AUTRE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurSod — true → success, false → danger, null → secondary")
|
||||
void couleurSod() {
|
||||
assertEquals("success", bean.getCouleurSod(true));
|
||||
assertEquals("danger", bean.getCouleurSod(false));
|
||||
assertEquals("secondary", bean.getCouleurSod(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Defaults — mode = ORG, plage = 30 derniers jours")
|
||||
void defaults() {
|
||||
assertEquals("ORG", bean.getMode());
|
||||
// plage from 30 jours avant maintenant — vérifié pas null
|
||||
org.junit.jupiter.api.Assertions.assertNotNull(bean.getFrom());
|
||||
org.junit.jupiter.api.Assertions.assertNotNull(bean.getTo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package dev.lions.unionflow.client.view;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RoleDelegationBeanTest {
|
||||
|
||||
private RoleDelegationBean bean;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
bean = new RoleDelegationBean();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCouleurStatut — ACTIVE/REVOQUEE/EXPIREE/null/inconnu")
|
||||
void couleur() {
|
||||
assertEquals("success", bean.getCouleurStatut("ACTIVE"));
|
||||
assertEquals("danger", bean.getCouleurStatut("REVOQUEE"));
|
||||
assertEquals("warning", bean.getCouleurStatut("EXPIREE"));
|
||||
assertEquals("secondary", bean.getCouleurStatut(null));
|
||||
assertEquals("secondary", bean.getCouleurStatut("AUTRE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Defaults — rôle TRESORIER, dates initialisées")
|
||||
void defaults() {
|
||||
assertEquals("TRESORIER", bean.getRoleDelegue());
|
||||
assertNotNull(bean.getDateDebut());
|
||||
assertNotNull(bean.getDateFin());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user