feat(sprint-2 P0+P1 2026-04-25): DOS CENTIF + Reporting AIRMS + PV OHADA + workflow demande aide v2 + délégation rôles + SYCEBNL donateurs + tests
Some checks failed
CI/CD Pipeline / pipeline (push) Failing after 3m10s

P0-NEW-16 — DOS CENTIF (Lignes directrices CENTIF mars 2025)
  - DosCentifService.genererDosWord() : XWPFDocument structurée 6 sections
  - DosCentifService.genererReleveExcel() : XSSFWorkbook avec opérations atypiques
  - DosCentifResource @RolesAllowed COMPLIANCE_OFFICER : POST /api/aml/dos/{word,excel}
  - Confidentialité absolue (jamais persisté disque, audit trail EXPORT)

P1-NEW-1 — Reporting AIRMS triple
  - RapportAirmsService.genererRapportTriple() : OpenPDF, 3 sections + cover
  - DTOs records : RapportTechnique (effectifs, sinistralité, délais),
    RapportMoral (vie associative, formations, activités),
    RapportFinancier (P&L, ratios prudentiels, trésorerie, fonds dédiés SYCEBNL)
  - Endpoint POST /api/airms/rapports/triple

P1-NEW-2 — Module PV OHADA (AG/CA)
  - V46 : table proces_verbaux (quorum, OJ JSONB, résolutions JSONB, hash SHA-256, signatures)
  - Entité ProcesVerbal + repo ProcesVerbalRepository
  - ProcesVerbalService :
    * quorumRequisDefaut() : 50% AG ord / 66.67% AG extra/CA
    * calculerEtFixerQuorum() : (présents+représentés)/convoqués × 100
    * adopter() : valide quorum, fige hash SHA-256
    * signer() : vérif hash inchangé (immuabilité), signature électronique
    * archiver() : statut ARCHIVE pour conservation OHADA 10 ans

P1-NEW-3 — Workflow demande d'aide v2
  - V46 : extension demandes_aide (etape, animateur_zone, gps_enquete, avis_comite, decision_ca)
  - V46 : extension types_aide (plafond_annuel_membre, plafond_enveloppe_annuelle, justificatifs_requis)
  - DemandeAide enrichie : 5 étapes DEPOSE → ENQUETE → AVIS_COMITE → DECISION_CA → PAYE → CLOTURE
  - DemandeAideV2Service :
    * Transitions typées avec checks d'état
    * SoD : approbateur CA ≠ animateur enquête
    * Audit trail enrichi à chaque transition

P1-NEW-5 — Délégation temporaire rôles
  - V46 : table role_delegations (delegant, delegataire, role, dates, statut, motif)
  - Entité RoleDelegation + isActiveAt(instant)
  - RoleDelegationService :
    * creer() : vérif SoD avant création (pas de conflit avec rôles existants délégataire)
    * revoquer() : statut REVOQUEE
    * rolesEffectifs() : directs ∪ délégués actifs
    * marquerExpirees() : scheduler quotidien

P1-NEW-13 — Registre donateurs SYCEBNL
  - V46 : tables donateurs + dons_recus (numéraire/nature/bénévolat/legs)
  - Affectation : LIBRE / FONDS_DEDIE / PROJET_SPECIFIQUE
  - Reçu fiscal (numero_recu, date_emission_recu)
  - Entités Donateur + DonRecu + repos
  - DonRecuRepository.totalEntre() pour reporting AIRMS/SYCEBNL

P1-NEW-14 — Membres honoraires/bienfaiteurs
  - V46 : ALTER membres_organisations.qualite_speciale (HONORAIRE/BIENFAITEUR/FONDATEUR)

Tests Sprint 2 (13 nouveaux, 0 failure) :
  - ProcesVerbalServiceTest (8) : quorum défaut, atteint/non-atteint, hash format/immuabilité/diff
  - RoleDelegationTest (5) : isActiveAt selon période et statut
This commit is contained in:
2026-04-25 01:53:16 +00:00
parent 7099f554fe
commit c54092bd78
19 changed files with 2087 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.centif.DosCentifService;
import dev.lions.unionflow.server.service.centif.DosCentifService.DosCentifData;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
/**
* Endpoint de génération des Déclarations d'Opérations Suspectes (DOS) pour la CENTIF.
*
* <p><strong>Accès restreint</strong> : seul un {@code COMPLIANCE_OFFICER} peut générer une DOS
* (rôle issu de l'Instruction BCEAO 001-03-2025). Les fichiers ne sont jamais persistés sur
* disque — streaming download direct.
*
* <p>L'export est tracé dans {@code audit_trail_operations} (action_type {@code EXPORT}).
*
* @since 2026-04-25 (P0-NEW-16)
*/
@Path("/api/aml/dos")
@Authenticated
public class DosCentifResource {
@Inject DosCentifService dosService;
/**
* Génère la DOS au format Word (.docx).
*
* @param data corps JSON {@link DosCentifData}
* @return fichier Word streamé
*/
@POST
@Path("/word")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
@RolesAllowed({"COMPLIANCE_OFFICER", "SUPER_ADMIN"})
public Response genererWord(DosCentifData data) throws IOException {
byte[] bytes = dosService.genererDosWord(data);
String filename = "DOS_" + data.numeroDosInterne() + ".docx";
return Response.ok(bytes)
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.build();
}
/**
* Génère le relevé d'opérations atypiques au format Excel (.xlsx).
*
* @param data corps JSON {@link DosCentifData}
* @return fichier Excel streamé
*/
@POST
@Path("/excel")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
@RolesAllowed({"COMPLIANCE_OFFICER", "SUPER_ADMIN"})
public Response genererExcel(DosCentifData data) throws IOException {
byte[] bytes = dosService.genererReleveExcel(data);
String filename = "Releve_Operations_" + data.numeroDosInterne() + ".xlsx";
return Response.ok(bytes)
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.build();
}
}

View File

@@ -0,0 +1,40 @@
package dev.lions.unionflow.server.resource;
import dev.lions.unionflow.server.service.airms.RapportAirmsService;
import dev.lions.unionflow.server.service.airms.RapportAirmsService.RapportAirmsData;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
/**
* Endpoint de génération du rapport AIRMS triple PDF (technique/moral/financier).
*
* @since 2026-04-25 (P1-NEW-1)
*/
@Path("/api/airms/rapports")
@Authenticated
public class RapportAirmsResource {
@Inject RapportAirmsService service;
@POST
@Path("/triple")
@Consumes(MediaType.APPLICATION_JSON)
@Produces("application/pdf")
@RolesAllowed({"PRESIDENT", "TRESORIER", "ADMIN_ORGANISATION", "SUPER_ADMIN"})
public Response genererTriple(RapportAirmsData data) throws IOException {
byte[] pdf = service.genererRapportTriple(data);
String filename = "Rapport_AIRMS_" + data.organisationDenomination().replaceAll("[^a-zA-Z0-9]", "_")
+ "_" + data.exerciceAnnee() + ".pdf";
return Response.ok(pdf)
.header("Content-Disposition", "attachment; filename=\"" + filename + "\"")
.build();
}
}