dahoud
4d400dc48d
feat(sprint-7 P1-NEW-15 2026-04-25): PI-SPI Readiness check (8 vérifications) + endpoint admin
...
CI/CD Pipeline / pipeline (push) Failing after 3m46s
Service ops/compliance pour valider l'état d'intégration PI-SPI BCEAO avant activation production.
PispiReadinessService — 8 vérifications structurées
- OAUTH2_CREDENTIALS (BLOCKING) : client_id + client_secret
- API_KEY (BLOCKING) : header X-API-Key obligatoire BCEAO
- MTLS_KEYSTORE (BLOCKING) : path + password + fichier .p12 existant
- MTLS_TRUSTSTORE (WARNING) : configuré + fichier existant (fallback cacerts si absent)
- WEBHOOK_SECRET (BLOCKING) : HMAC-SHA256 webhooks
- BASE_URL (WARNING) : sandbox vs production détecté automatiquement
- TAUX_EUR_XOF (WARNING) : taux récent ≤ 7 jours (sinon obsolète)
- PROVIDER_CONFIGURED (BLOCKING) : PispiAuth.isConfigured() = true (synthèse)
Statut global agrégé
- READY — tous PASS
- DEGRADED — uniquement WARNING en échec (sandbox OK, prod à risque)
- BLOCKED — au moins un BLOCKING en échec (sandbox impossible)
Endpoint /api/admin/pispi/readiness
- @RolesAllowed SUPER_ADMIN, COMPLIANCE_OFFICER
- HTTP 200 si READY/DEGRADED, 503 si BLOCKED
- Réponse JSON : globalStatus, baseUrl, checks détaillés, blockingIssues, warnings
Helpers publics ajoutés (pour readiness sans casser l'encapsulation)
- PispiAuth : hasClientId(), hasClientSecret(), hasApiKey(), keystorePath(), keystorePassword(), truststorePath(), truststorePassword()
- PispiSignatureVerifier : hasWebhookSecret()
Tests (15/15 verts)
- BLOCKED tout vide, READY tout configuré, DEGRADED warnings only
- Checks individuels OAuth2/ApiKey/Keystore (path absent / fichier inexistant / OK), Truststore warning, Webhook, BaseUrl sandbox/prod, Taux récent/obsolète, Provider
2026-04-25 10:48:59 +00:00
dahoud
86842f27af
feat(sprint-6 P2-NEW-7 2026-04-25): multi-devise + KYC non-résident diaspora + tests
...
CI/CD Pipeline / pipeline (push) Failing after 4m11s
Ouvre UnionFlow à la diaspora UEMOA (France, USA, Canada, UK, Suisse...).
Entités & migration
- Enum Devise (10 valeurs : XOF, XAF, EUR, USD, GBP, CAD, CHF, GHS, NGN, MAD)
- Zones : UEMOA, CEMAC, CEDEAO, EUROPE, AMERIQUE, MAGHREB
- DEVISES_INTERNATIONALES : EUR/USD/GBP/CAD/CHF (déclenchent AML international)
- Entité TauxChange (devise_source × devise_cible × date_validite, taux NUMERIC(18,8))
- Repository : trouverExact, trouverPlusRecent (≤ date)
- V49 :
- Table taux_change (contrainte unicité paire+date, devises distinctes)
- Seed BCEAO_FIXED EUR↔XOF + taux indicatifs USD/GBP/CAD au 2026-04-25
- Membres : pays_residence (ISO-3), numero_passeport, numero_fiscal_etranger, est_diaspora, devise_preferee
- KycDossiers : pays_origine_fonds, justificatif_residence_etrangere, niveau_due_diligence (SIMPLIFIE/STANDARD/RENFORCE)
DeviseConversionService
- Stratégie de résolution : direct → inverse → pivot via XOF → fallback récent ≤ date
- Cache thread-safe (ConcurrentHashMap, TTL 1h)
- TauxIntrouvableException si aucun taux résolvable
- invaliderCache() pour reload après import batch
KycDiasporaService
- validerCoherence : passeport obligatoire si diaspora, pays_residence ≠ UEMOA, format passeport regex
- determinerNiveauDueDiligence (Instr. BCEAO 001-03-2025) :
- PEP → RENFORCE
- Diaspora pays sécurisés (UE/G7/Asie) → STANDARD
- Diaspora FATF grey-list → RENFORCE
- Diaspora pays inconnu → RENFORCE par prudence
- depasseSeuilAmlInternational : seuil 1000 EUR équivalent, false sur devises locales
- PAYS_UEMOA hardcodé (8 pays), PAYS_GREY_LIST_FATF snapshot 2026-04-25
Tests Sprint 6 (34/34 verts)
- DeviseTest : 5 tests (référence, internationales, zones, libellés)
- DeviseConversionServiceTest : 10 tests (identité, direct, inverse, pivot XOF, fallback récent, cache, invalider, exception, inputs invalides)
- KycDiasporaServiceTest : 19 tests (cohérence valide/sans passeport/pays UEMOA/pays étranger, due diligence PEP/FRA/grey-list/inconnu/UEMOA, seuil EUR/USD avec taux/sans taux/XOF/null)
2026-04-25 10:33:05 +00:00
dahoud
a0b2690c17
feat(sprint-5 P2-NEW-3 2026-04-25): reporting trimestriel ControleurInterne automatisé + scheduler + tests
...
CI/CD Pipeline / pipeline (push) Failing after 3m26s
Rapport trimestriel agrégé pour le Contrôleur Interne (source AG + inspections BCEAO/ARTCI).
Entités & migration
- RapportTrimestrielControleurInterne (BaseEntity + JSONB contenu + bytea PDF + hash SHA-256)
- V48 : table rapports_trimestriels_controleur_interne, contraintes, indexes
- Repository : trouverParOrgAnneeTrimestre, listerParOrgAnnee, listerNonSignes
Service RapportTrimestrielService
- genererRapport(orgId, annee, trimestre) : agrège ComplianceSnapshot + DOS CENTIF + formations LBC/FT + anomalies SoD audit trail + demandes aide
- construireJson : structure conformite/activite/alertes
- genererPdf : OpenPDF A4 avec sections 1.Conformité 2.Activité 3.Alertes + bloc signature
- signer(rapportId, signataireId) : calcule SHA-256 du JSON, fige le statut
- archiver(rapportId) : passe SIGNE → ARCHIVE
- Idempotent en DRAFT (régénération possible) ; immuable en SIGNE/ARCHIVE
Resource RapportTrimestrielResource
- GET /api/rapports/trimestriel?orgId&annee — lister
- POST /api/rapports/trimestriel/generer — CONTROLEUR_INTERNE / SUPER_ADMIN
- POST /api/rapports/trimestriel/{id}/signer — CONTROLEUR_INTERNE
- POST /api/rapports/trimestriel/{id}/archiver — CONTROLEUR_INTERNE / PRESIDENT
- GET /api/rapports/trimestriel/{id}/pdf — application/pdf
Scheduler RapportTrimestrielScheduler
- @Scheduled cron 0 17 2 1 1,4,7,10 ? — 1er jan/avr/jul/oct à 02:17
- Génère pour toutes les orgs actives le trimestre précédent
- Override possible via unionflow.reporting.trimestriel.cron
- ConcurrentExecution.SKIP
RoleConstant
- Ajout CONTROLEUR_INTERNE, COMPLIANCE_OFFICER, COMMISSAIRE_COMPTES (utilisés depuis V45)
Tests Sprint 5 (20/20 verts)
- RapportTrimestrielServiceTest : 15 tests (debutTrimestre, finTrimestre, hash SHA-256, JSON alertes, PDF non vide)
- RapportTrimestrielSchedulerTest : 5 tests (trimestrePrecedent — incluant rollover année)
2026-04-25 10:13:07 +00:00
dahoud
b0ee8881fb
feat(sprint-4 P1-NEW-8/9 2026-04-25): docker-compose Keycloak 26.6.1 + fix Mockito strict-stubbing test KC
...
CI/CD Pipeline / pipeline (push) Failing after 4m25s
P1-NEW-8/9 — Préparation migration Keycloak 23 → 26.6.1 + Organizations GA
- docker-compose.kc26.yml : compose alternatif KC 26.6.1 avec --features=organization
- Bind-mount realm import depuis src/main/resources/keycloak/realms
- Healthcheck readiness probe + KC_HEALTH_ENABLED
- Postgres 15 dédié, volume persistant
- Réf : ARCH_KEYCLOAK_26.md
- Permet validation locale avant cutover (compose dev intact)
Fix tests KeycloakAdminHttpClientTest (4 erreurs Mockito UnnecessaryStubbing)
- Helper mockResponse : lenient() sur body() (lu uniquement sur paths erreur/JSON parsing)
- 9/9 tests passent désormais
État Sprint 4 (déjà livré dans sessions antérieures, validé ici) :
- Entité Organisation.keycloakOrgId + migration V37
- Service MigrerOrganisationsVersKeycloakService (idempotent, 9 tests)
- OrganisationContextResolver (parsing claim 'organization' JWT, 12 tests)
- AdminKeycloakOrganisationResource (endpoint admin migration)
- KeycloakAdminHttpClient (sessions, 9 tests)
Tests Sprint 4 : 36/36 passent (resolver 12 + migration 9 + admin HTTP 9 + context holder 6)
Reste pour migration finale (post-Sprint 4) :
- Validation manuelle docker compose -f docker-compose.kc26.yml up -d
- Bascule production : remplacer compose KC23 par KC26
- Refactoring backend : suppression OrganisationContextFilter + headers custom
2026-04-25 08:58:00 +00:00
dahoud
8b589477ec
feat(sprint-3 P1+P2 2026-04-25): compliance dashboard + PEP screening + formations LBC/FT + goAML XML + tests
...
CI/CD Pipeline / pipeline (push) Failing after 3m15s
P1-NEW-11 — Tableau de bord conformité
- ComplianceDashboardService : score 0-100 avec 9 indicateurs pondérés (compliance officer, AG annuelle, rapport AIRMS, dirigeants CMU, taux KYC, taux formation LBC/FT, CAC, FOMUS-CI, couverture UBO)
- ComplianceDashboardResource : GET /api/compliance/dashboard
P1-NEW-11 — PEP screening externe
- PepScreeningProvider (interface) + records PepScreeningResult / PepMatch
- InMemoryPepScreeningProvider : fallback dev avec similarité Levenshtein normalisée (seuil 0.80)
- PepScreeningService : cache LRU 5000 entrées, TTL 24h
- Pluggable via @Alternative @Priority pour Youverify / ComplyAdvantage en prod
P1-NEW-12 — Module formation LBC/FT obligatoire annuelle
- FormationLbcFt + ParticipationFormationLbcFt (entités)
- FormationLbcFtService : creer, inscrire, marquerPresent, certifier (score >= 70), estCertifieAnneeCourante
- Repositories : trouverCertificationAnnee, findATenir
- V47 migration : formations_lbcft, participations_formation_lbcft, pep_screening_cache
P2-NEW-4 — goAML XML (anticipation adoption CI)
- GoAmlXmlService : génération XML standard ONUDC (report, transaction, t_person, t_account)
- Reporting person anonymisé ([REDACTED], rôle Compliance Officer)
- POST /api/aml/dos/goaml @RolesAllowed(COMPLIANCE_OFFICER, SUPER_ADMIN)
Tests Sprint 3 (15 tests, 100%) :
- FormationLbcFtTest : 2 tests (builder, override type)
- GoAmlXmlServiceTest : 4 tests (XML non vide, champs clés, anonymisation, country code CIV)
- InMemoryPepScreeningProviderTest : 9 tests (match, nationalité, vide, similarité Levenshtein)
2026-04-25 08:37:06 +00:00
c54092bd78
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
...
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
2026-04-25 01:53:16 +00:00
7099f554fe
feat(p0-2026-04-25): mobile SPKI pinning + Play Integrity/App Attest + tests Sprint 1
...
CI/CD Pipeline / pipeline (push) Failing after 3m7s
P0-NEW-21 — SPKI Pinning + rotation Firebase Remote Config (mobile)
- lib/core/security/spki_pinning_service.dart : digest SHA-256 SPKI/cert
- Liste de pins active depuis Firebase Remote Config (key 'spki_pins')
- Fallback statique au bundle si Firebase indisponible
- Multi-pin (leaf + backup + intermediate) pour transitions sans downtime
- Hosts pinnés : api.lions.dev, security.lions.dev
- Câblé dans ApiClient._configureSslPinning() (remplace check CN obsolète)
P0-NEW-22 — Play Integrity (Android) + App Attest (iOS) (mobile)
- lib/core/security/app_device_integrity_service.dart
- Token attestation court (cache 60s) avec challenge backend
- Bypass automatique en kDebugMode
- À compléter avec un Dio interceptor X-Device-Integrity-Token avant go-live
pubspec.yaml :
- freerasp 7.0.0 → 7.5.1
- +app_device_integrity 1.1.0
- +firebase_core 3.6.0 + firebase_remote_config 5.1.3
Tests Sprint 1 (40 tests, 0 failure) :
- ReferentielComptableTest (6 cas) : defaultFor mapping
- AmlSeuilsTest (10 cas) : seuils 10M/5M/1M, pays UEMOA, depasseSeuil
- SoDPermissionCheckerTest (13 cas) : validation distincte, combinations interdites,
compliance officer eligibility
- PispiRtpRequestTest (6 cas) : validation contraintes
- PispiRtpResponseTest (5 cas) : helpers status
2026-04-25 01:24:53 +00:00
144137656f
feat(p0-2026-04-25): PI-SPI auth 3-facteurs + RTP + alias
...
CI/CD Pipeline / pipeline (push) Failing after 3m32s
P0-NEW-2 — Authentification PI-SPI 3-facteurs (spec sandbox developer.pispi.bceao.int)
- PispiAuth : OAuth2 + mTLS (PKCS12 keystore + truststore optionnel) + X-API-Key
- SSLContext TLS 1.3 + KeyManagerFactory + TrustManagerFactory
- HttpClient mTLS lazy + cache, HTTP/2, timeout 15s/30s
- isConfigured() pour bascule auto mode mock si secrets absents
P0-NEW-3 — Request To Pay (RTP — pain.013/014)
- dto/PispiRtpRequest (record validate())
- dto/PispiRtpResponse avec helpers isAccepted()/isRefused()/...
- PispiClient.initiateRtp() + getRtpStatus()
- Cas d'usage : appel cotisation initié par la SFD vers le membre
P0-NEW-4 — Gestion d'alias (téléphone/email → compte SFD)
- dto/PispiAlias (record + Types : PHONE_NUMBER/EMAIL/NATIONAL_ID/CUSTOM)
- PispiClient.resolveAlias() + createAlias() + revokeAlias()
- Cas d'usage : '+22507XXX@unionflow' ou 'cotisation-{slug}@unionflow'
PispiClient harmonisé : baseRequestBuilder() central avec 3 headers obligatoires, gestion 404 sur resolveAlias, timeouts 30s.
2026-04-25 01:18:46 +00:00
d8006c8425
feat(p0-2026-04-25): multi-référentiel comptable + UBO + audit trail + SoD + seuils AML
...
CI/CD Pipeline / pipeline (push) Failing after 3m11s
Sprint 1 P0 (consolidation 2026-04-25, ETAT_PROJET_METIER_2026-04-25.md) :
P0-NEW-9/10/11 — Multi-référentiel comptable
- enum ReferentielComptable (SYSCOHADA / SYCEBNL / PCSFD_UMOA)
- Organisation.referentielComptable + mapping defaultFor(typeOrganisation)
- V43 : colonne + check + index + mapping initial des orgs existantes
P0-NEW-13 — Bénéficiaires effectifs (UBO) — Instruction BCEAO 003-03-2025
- Entité BeneficiaireEffectif + repository
- V44 : table beneficiaires_effectifs (FK kyc_dossier, UBO + PEP + sanctions)
- Conservation 10 ans (directive 02/2015/CM/UEMOA)
P0-NEW-14 — Compliance Officer (Instruction BCEAO 001-03-2025)
- Organisation.complianceOfficerId + V43 colonne + index
P0-NEW-15 — Seuils AML alignés (Instruction BCEAO 002-03-2025)
- AmlSeuils : 10M FCFA intra-UEMOA / 5M FCFA entrée-sortie / 1M FCFA espèce
- Liste pays UEMOA ISO 3166-1
- Méthodes seuilApplicable() / depasseSeuil() / depasseSeuilEspece()
P0-NEW-17/18 — Rôles PRESIDENT + CONTROLEUR_INTERNE + suppléants
- V45 seed : PRESIDENT, VICE_PRESIDENT, CONTROLEUR_INTERNE, ANIMATEUR_ZONE, SECRETAIRE_ADJOINT, TRESORIER_ADJOINT
- Catégories GOUVERNANCE / CONTROLE / OPERATIONNEL
P0-NEW-19 — Audit trail enrichi (SYSCOHADA + AUDSCGIE)
- V45 : table audit_trail_operations (acteur, action, contexte multi-org, payload JSONB, SoD)
- Entité AuditTrailOperation + AuditTrailOperationRepository
- AuditTrailService (log avec contexte automatique depuis OrganisationContextHolder)
- OrganisationContextHolder enrichi (roleActif, currentUserId, currentUserEmail)
P0-NEW-20 — SoD (Separation of Duties) — SYSCOHADA + AUDSCGIE + BCEAO Circulaire 03-2017
- SoDPermissionChecker.checkValidationDistinct() (4-eyes principle)
- .checkRoleCombination() (combinaisons interdites : Trésorier+Président, etc.)
- .checkComplianceOfficerEligibility() (Instruction BCEAO 001-03-2025)
- SoDCheckResult record avec audit trail automatique
P0-NEW-24 — Champ numero_cmu sur Membre (Loi 2014-131 CI)
- Membre.numeroCMU + V43 colonne + check format 11 caractères + index
- Auto-déclaration (pas d'API publique CNAM disponible)
BUILD SUCCESS.
2026-04-25 01:15:25 +00:00
6e9841b3bb
fix(disaster-recovery 2/2): restaurer 242 fichiers Java modifiés par a72ab54
...
CI/CD Pipeline / pipeline (push) Failing after 3m22s
Suite à la récupération précédente (044ca4b ) qui n'avait restauré que les
fichiers SUPPRIMÉS, ce commit restaure les MODIFICATIONS d'entités/services
qui étaient nécessaires pour que les fichiers restaurés compilent.
Restaurés depuis a72ab54^ (= 31330d9 + corrections) :
- Entities : Organisation, FormuleAbonnement, AuditService, MembreOrganisation, SouscriptionOrganisation, etc.
- Services : MigrerOrganisationsVersKeycloakService, ComptabilitePdfService, KycAmlService, AuditService.logKycRisqueEleve, etc.
- Resources : PaiementUnifieResource, etc.
Backend compile désormais (BUILD SUCCESS).
2026-04-25 01:05:08 +00:00
044ca4bd7e
fix(disaster-recovery): restaurer 134 fichiers accidentellement supprimés par a72ab54
...
CI/CD Pipeline / pipeline (push) Failing after 3m46s
Le commit a72ab54 (chore docker Dockerfile racine) a involontairement balayé
des fichiers du commit 31330d9 (PI-SPI, KYC, RLS, mutuelle parts, comptabilité
PDF) lors d'un git add -A trop large.
Restauration de l'intégralité des fichiers depuis a72ab54^ :
- 11 migrations Flyway V32-V42 (parts sociales, SYSCOHADA, Keycloak Org Id, KYC, RLS, Provider défaut, FCM, App DB Roles)
- Package payment/pispi/ complet (PispiAuth, PispiClient, PispiIso20022Mapper, PispiSignatureVerifier, PispiWebhookResource, dto/Pacs008Request, dto/Pacs002Response, PispiPaymentProvider)
- Package payment/{wave,orangemoney,mtnmomo}/* (PaymentProvider impls)
- Package payment/orchestration/ (PaymentOrchestrator, PaymentProviderRegistry)
- Entités KycDossier, mutuelle/parts/* (ComptePartsSociales, TransactionPartsSociales)
- Mappers, repositories, resources associés
- Services KycAmlService, ComptabilitePdfService, ReleveComptePdfService, InteretsEpargneService
- AdminKeycloakOrganisationResource, KycResource, PaiementUnifieResource
- Tests unitaires PI-SPI, KYC, mutuelle parts
2026-04-25 01:00:03 +00:00
b434282000
docs: Quarkus 3.15.1→3.27.3 LTS, Java 17→21, lionsctl -j 21, Dockerfile racine, pré-requis infra
CI/CD Pipeline / pipeline (push) Failing after 4m9s
2026-04-24 18:05:43 +00:00
a72ab54abd
chore(docker): add root Dockerfile pinning ubi8/openjdk-21:1.21 + UID 1001 for lionsctl pipeline
CI/CD Pipeline / pipeline (push) Failing after 4m2s
2026-04-24 16:19:25 +00:00
fb3a32817b
chore(quarkus-327): bump to Quarkus 3.27.3 LTS, make pom autonomous, fix 3 tests (NPE guard, equalsHashCode with shared refs), rename deprecated config keys
2026-04-23 14:45:54 +00:00
dahoud
8cec38f7b3
fix(monitoring): corriger calcul CPU — getProcessCpuLoad au lieu de loadAverage/processors
...
Le calcul précédent `getSystemLoadAverage() / getAvailableProcessors() * 100`
utilisait :
- getSystemLoadAverage() : charge 1min du NODE Linux hôte entier (12 CPU sur prod VPS)
- getAvailableProcessors() : limite conteneur K8s (1 CPU pour le pod UnionFlow)
Résultat : load 1.5 sur le node → cpuUsage = (1.5 / 1) * 100 = 150% capé à 100%.
Déclenchement constant d'alertes "CPU 100%" (19 faux positifs / 24h sur prod
le 20-21/04/2026) dès que le node fait autre chose que dormir.
Correctif : utilise com.sun.management.OperatingSystemMXBean.getProcessCpuLoad()
qui retourne la charge CPU du process JVM courant (0.0-1.0), conscient du
conteneur. Branche -1 préservée (API non dispo sur certaines JVM).
Tests mis à jour : AlertMonitoringServiceMockStaticCoverageTest injecte
désormais un com.sun.management.OperatingSystemMXBean et mocke
getProcessCpuLoad() (compatible mock-maker-inline déjà configuré).
AlertMonitoringServiceTest conserve sa logique OS-agnostique via des
thresholds extrêmes (-1 toujours / 100 jamais).
2026-04-21 13:38:31 +00:00
dahoud
31330d95e9
feat: accumulated work — PI-SPI, KYC, RLS, mutuelle parts, comptabilité PDF + startup fixes
...
## PI-SPI BCEAO (P0.3 — deadline 30/06/2026)
- package payment/pispi/ complet : PispiAuth (OAuth2), PispiClient (HTTP brut),
PispiIso20022Mapper (pacs.008/002), PispiSignatureVerifier (HMAC-SHA256),
PispiWebhookResource (/api/pispi/webhook), DTOs ISO 20022
- PaymentOrchestrator + PaymentProviderRegistry pour l'orchestration multi-provider
- Mode mock automatique si credentials absents (dev)
## KYC AML
- entity/KycDossier, KycResource, KycAmlService + tests
- Migration V38 (create_kyc_dossier_table)
## RLS (PostgreSQL Row-Level Security) — isolation multi-tenant
- RlsConnectionInitializer, RlsContextInterceptor, @RlsEnabled annotation
- Migration V39 (PostgreSQL RLS Tenant Isolation) + V42 (app DB roles)
- Tests unitaires RlsConnectionInitializerTest, RlsContextInterceptorTest
- Tests d'intégration RlsCrossTenantIsolationTest (@QuarkusTest + IntegrationTestProfile)
## Mutuelle — Parts sociales
- entity/mutuelle/parts/ComptePartsSociales, TransactionPartsSociales
- Service, resource, mapper, repository + tests
- InteretsEpargneService + ReleveComptePdfService
## Comptabilité PDF
- ComptabilitePdfService (OpenPDF), ComptabilitePdfResource
- Tests ComptabilitePdfServiceTest, ComptabilitePdfResourceTest
## Migrations Flyway (SYSCOHADA + Keycloak Orgs)
- V36 SYSCOHADA Plan Comptable Complet : seeds comptes standards UEMOA,
trigger init_plan_comptable_organisation, alignement schéma V1 → entités
- V37 keycloak_org_id sur organisations (P0.2 migration KC 26)
- V40 provider_defaut sur FormuleAbonnement
- V41 fcm_token sur utilisateurs (FCM notifications push)
## Fixes startup (SmallRye Config 3.20 + schéma)
- 8× @ConfigProperty(defaultValue = "") → Optional<String>
(firebase, pispi.*, mtnmomo, orange) — empty default rejetés par SmallRye 3.20
- application.properties : mappings secrets env var sous %prod. uniquement
- V36 : drop colonne obsolète 'numero' de V1 quand Hibernate a créé 'numero_compte'
- V36 : remplacement UNIQUE global sur journaux_comptables.code par composite
(organisation_id, code) pour autoriser plusieurs orgs avec code 'ACH'/'VTE'/etc
- V39 : escape placeholder ${VAR} → <VAR> dans lignes commentées
(Flyway parser évalue les placeholders même dans les commentaires)
- V41 : table 'membres' → 'utilisateurs' (nom correct selon entité Membre)
- JournalComptable entity : @UniqueConstraint composite au lieu de unique=true
- MembreResource : example @Schema JSON valide (['...'] → [])
- IntegrationTestProfile : auto-détection Docker via `docker info`, fallback
vers PostgreSQL local sans DevServices
## Dev config
- application-dev.properties : quarkus.devservices.enabled=false +
quarkus.kafka.devservices.enabled=false (pas besoin de Docker pour dev)
- quarkus.flyway.placeholder-replacement=false
- Secrets dev (wave.*, firebase, pispi) en mode mock automatique
## Phase 8 tests (complète)
- 170 fichiers modifiés/ajoutés, 23425+ insertions
- Tests RBAC (@QuarkusTest) pour MembreResource lifecycle
- Tests OrganisationContextFilter multi-org
- Tests SouscriptionQuotaOptionC, KycAmlService, EmailTemplate, etc.
Résultat : Backend démarre en 64s sur port 8085 avec 36 features installées.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-21 12:40:55 +00:00
dahoud
9a53ce4077
feat: sécuriser endpoints organisations + stats par org + fix IP dev
...
- PUT /{id}: check appartenance ADMIN_ORGANISATION (403 si pas membre)
- GET /statistiques: stats scoped à l'org active pour ADMIN_ORGANISATION
- OrganisationService.obtenirStatistiquesParOrganisation(): stats mono-org
- MembreOrganisationRepository.findByMembreEmailAndOrganisationId()
- application-dev: IP 192.168.1.145→localhost pour Keycloak
2026-04-18 08:07:04 +00:00
dahoud
9f14c2e345
refactor: supprimer setTypeAssociation/setTypeLibelle doublons dans OrganisationService
...
- OrganisationService.convertToResponse: ne plus setter typeAssociation ni typeLibelle
- Utiliser uniquement typeOrganisation et typeOrganisationLibelle
- Corriger tests: assertions sur typeOrganisationLibelle au lieu de typeLibelle
2026-04-17 20:00:34 +00:00
dahoud
4b2b326afe
refactor: nettoyer terminologie entité→organisation et corriger mapping TONTINE
...
- ADMIN_ENTITE→ADMIN_ORGANISATION dans Javadoc et README
- OrganisationModuleService: retirer TONTINE de MUTUELLE (BCEAO-réglementé, incompatible)
- Ajouter TONTINE aux ASSOCIATION et CLUB_SERVICE (pratique courante Afrique de l'Ouest)
- V18 migration: aligner modules_requis avec le code Java
2026-04-17 19:19:48 +00:00
dahoud
194a1e7017
fix(prod): ajouter flyway.repair-at-start pour corriger checksum V18 modifié
...
La V18 a été modifiée (fusion MUTUELLE_EPARGNE+CREDIT → MUTUELLE).
En prod, le checksum en DB ne correspond plus → Flyway refuse de démarrer.
repair-at-start met à jour la schema_history automatiquement.
2026-04-17 00:23:51 +00:00
dahoud
a4e5b6af12
fix(souscription): réécrire activerAdminOrganisation — promouvoir le payeur, pas un membre aléatoire
...
Problème : après paiement Wave, le membre restait SIMPLEMEMBER car :
1. La méthode bouclait sur TOUS les liens de l'org et promouvait le
premier non-ACTIF (pas forcément le payeur)
2. Si aucun lien n'existait, créait un lien avec SIMPLEMEMBER sans promotion
3. Early return après la première promotion → si le mauvais membre était
promu, le payeur restait bloqué
Nouvelle logique :
1. Identifier le caller (JWT email) = le membre qui a réellement payé
2. Vérifier/créer son lien MembreOrganisation (activer si en attente)
3. Promouvoir ce membre spécifique en ORGADMIN (DB)
4. Syncer Keycloak ADMIN_ORGANISATION (non-bloquant)
Résultat : après paiement, le payeur est automatiquement promu ORGADMIN
avec statut ACTIF et accède directement à son dashboard.
2026-04-16 14:24:17 +00:00
dahoud
4a1ca88517
test: corriger constructeurs OrganisationSummaryResponse 10→12 args (ville+pays)
...
3 fichiers de test utilisaient l'ancien constructeur à 10 arguments.
Ajout de null, null pour ville et pays dans tous les appels.
2026-04-16 14:07:28 +00:00
dahoud
51bb996eef
fix(org): passer ville + pays au constructeur OrganisationSummaryResponse
...
Ajout des 2 champs manquants dans convertToSummaryResponse() pour que
la liste des organisations affiche la localisation.
2026-04-16 12:29:37 +00:00
dahoud
48604bbbc6
feat(types-ref): auto-génération du code technique depuis le libellé
...
TypeReferenceService.creer() :
- Si code non fourni ou vide : auto-généré depuis le libellé via genererCodeDepuisLibelle()
- Si code fourni : nettoyé via normaliserCode() (strip accents, UPPER_SNAKE_CASE)
- Dédoublonnage automatique : si le code existe, suffixe _2, _3, etc.
Méthodes ajoutées :
- genererCodeDepuisLibelle(String) : libellé → UPPER_SNAKE_CASE sans accents
Ex: 'Mutuelle d''Épargne' → 'MUTUELLE_D_EPARGNE'
- normaliserCode(String) : Normalizer.NFD + strip diacritics + [^A-Za-z0-9] → _
- assurerUniciteCode(String, String, UUID) : suffixe incrémental si doublon
Les types système (est_systeme=true, seeded en V18) gardent leurs codes figés.
Seuls les types créés par l'utilisateur bénéficient de l'auto-génération.
2026-04-16 10:20:19 +00:00
dahoud
15479c0432
fix(types-org): fusionner MUTUELLE_EPARGNE + MUTUELLE_CREDIT → MUTUELLE unifié
...
Une mutuelle (MEC/COOPEC) fait TOUJOURS épargne ET crédit conjointement
dans le cadre réglementaire BCEAO/UEMOA. La séparation en deux types
n'avait pas de réalité terrain.
V18 corrigée :
- MUTUELLE_EPARGNE + MUTUELLE_CREDIT supprimés
- MUTUELLE ajouté : modules EPARGNE,CREDIT,FINANCE,LCB_FT (complet)
- COOPERATIVE enrichi : ajout EPARGNE + VOTES (réalité terrain — les coopératives
ont des AG avec votes et proposent souvent de l'épargne à leurs membres)
Passe de 17 → 16 types d'organisation.
Le mapping mobile _mapTypeOrganisationBilling garde les anciens codes en fallback
pour rétrocompatibilité.
2026-04-16 10:13:29 +00:00
dahoud
5f12ab3406
fix(audit): PorteeAudit.GLOBAL → PLATEFORME + archiver docs obsolètes
...
- AuditService.logMembreDesactive : PorteeAudit.GLOBAL n'existe pas dans l'enum,
remplacé par PLATEFORME (même sémantique — audit admin plateforme)
- docs/archive/ : archivé AUDIT_MIGRATIONS_PRECISE, CONSOLIDATION_MIGRATIONS_FINALE,
NETTOYAGE_MIGRATIONS_RAPPORT, TESTS_CONNUS_EN_ECHEC, JACOCO_TESTS_MANQUANTS
- docs/FLYWAY_MIGRATIONS_GUIDE.md : consolidé depuis AUDIT_MIGRATIONS.md
2026-04-16 09:31:14 +00:00
dahoud
316e683c46
chore(gitignore): dédupliquer + ajouter Maven cache + credentials + .env
...
- Retrait du doublon du.exe.stackdump (était présent 2 fois)
- *.stackdump (générique pour crashes bash/cygwin)
- Maven lastUpdated + _remote.repositories (caches de pulls échoués)
- *-credentials.json + application-secrets.properties (secrets)
- .env + .env.* (env vars)
- .quarkus-dev-ui-history (dev mode UI)
- macOS (.AppleDouble, .LSOverride)
2026-04-15 23:20:14 +00:00
dahoud
4e1a6d4007
test: couverture Messaging + Versement + ContactPolicy + MemberBlock
...
Tests unitaires pour les nouveaux modules :
- Entity tests : ContactPolicyTest, ConversationParticipantTest, MemberBlockTest,
VersementTest, VersementObjetTest
- Repository tests : ContactPolicyRepositoryTest, ConversationParticipantRepositoryTest,
MemberBlockRepositoryTest, VersementRepositoryTest
- Resource tests : MessagingResourceTest, VersementResourceTest
- Service tests : MessagingServiceTest, VersementServiceTest
2026-04-15 20:24:33 +00:00
dahoud
2f7bb545d0
chore: pom.xml + application.properties + tests + gitignore
...
- pom.xml : mise à jour dépendances
- application.properties : ajustements config
- MembreServiceTest, EntityCoverageTest : tests mis à jour pour nouveautés
- .gitignore : ajout du.exe.stackdump (dump Windows bash)
2026-04-15 20:24:16 +00:00
dahoud
66151b4fd1
feat(dashboard): DashboardServiceImpl + KafkaEventConsumer mis à jour
...
- DashboardServiceImpl : stats enrichies
- KafkaEventConsumer : consommation events pour refresh stats temps réel
- BackupRecordRepository, SystemLogRepository : petits ajustements
2026-04-15 20:24:05 +00:00
dahoud
6ff85bd503
feat(wave): webhooks + redirect handler
...
- WebhookWave : entité pour logs webhooks Wave (idempotence + audit)
- WaveRedirectResource : endpoint de retour après paiement Wave
(redirige vers l'app mobile avec le statut)
2026-04-15 20:23:58 +00:00
dahoud
e482ad5a4d
feat(admin): KeycloakAdminHttpClient + AdminUserService amélioré
...
- KeycloakAdminHttpClient (nouveau) : client HTTP natif (java.net.http.HttpClient)
pour contourner les problèmes de désérialisation avec RESTEasy sur certains
endpoints Keycloak 26+ (bruteForceStrategy, cpuInfo inconnus).
Utilise ObjectMapper avec FAIL_ON_UNKNOWN_PROPERTIES=false.
- AdminUserService : utilisation correcte de AdminUserServiceClient + AdminRoleServiceClient
avec AdminServiceTokenHeadersFactory pour l'auth.
- ModuleAccessFilter : améliorations de la logique @RequiresModule.
2026-04-15 20:23:50 +00:00
dahoud
9a270995ee
feat(system-config): persistance configuration système en DB
...
- Migration V29 : table system_config (key-value avec type/description)
- SystemConfigPersistence : entité pour stocker les paramètres système
- SystemConfigPersistenceRepository : findByKey + upsert
- SystemConfigService : lecture/écriture typée (String/Int/Bool) avec fallback defaults
- SystemResource : endpoints de config exposés aux SuperAdmins
2026-04-15 20:23:39 +00:00
dahoud
217021933e
fix(paiement): rendre colonnes legacy nullables + refactor Paiement/PaiementObjet
...
Migrations :
- V25 : numero_transaction nullable dans paiements (legacy V1 NOT NULL bloquant INSERT)
- V26 : autres colonnes legacy NOT NULL V1 (type_paiement, statut_paiement, etc.)
rendues nullables pour alignement avec l'entité Paiement
Refactor Paiement/PaiementObjet : mise à jour entités, repository, resource, service
pour cohérence avec le nouveau module Versement. Tests associés supprimés/ajustés.
2026-04-15 20:23:30 +00:00
dahoud
5d028a10bf
feat(versement): nouveau module Versement (paiements rattachés à des objets)
...
- Entités : Versement, VersementObjet (lien polymorphique vers cotisation/adhesion/etc.)
- VersementRepository : requêtes par membre, org, période
- VersementResource : endpoints REST /api/versements
- VersementService : logique métier (validation, rattachement objets)
- Migration V27 : ajout numeroTelephone sur versements
2026-04-15 20:23:17 +00:00
dahoud
719d45e1fe
feat(messaging): module messagerie unifié avec contact policies + member blocks
...
Refactor complet : fusion de Conversation + Message en un module Messaging unique
avec ContactPolicy (règles qui-peut-parler-à-qui) et MemberBlock (blocages utilisateur).
- Migration V28 : tables conversations/conversation_participants/messages/
contact_policies/member_blocks
- Nouvelles entités : ContactPolicy, ConversationParticipant, MemberBlock
(Conversation/Message mises à jour avec relations)
- Nouvelles repositories : ContactPolicyRepository, ConversationParticipantRepository,
MemberBlockRepository
- MessagingResource (nouveau) remplace ConversationResource + MessageResource
- MessagingService (nouveau) remplace ConversationService + MessageService
avec vérifications appartenance org + policies + blocages avant envoi
- Anciens fichiers Conversation/Message Resource/Service/Tests supprimés
2026-04-15 20:23:04 +00:00
dahoud
a650b372f1
chore: untrack target/ (déjà dans .gitignore mais tracké par erreur)
2026-04-15 20:17:37 +00:00
dahoud
aebf333421
chore(dev): revert IP LAN 192.168.1.13 → 192.168.1.145
2026-04-15 20:13:02 +00:00
dahoud
aa4350ffbb
feat(members): desactiverMembre cascade complète (Keycloak, Kafka, audit, mono-admin)
...
Refactor de MembreService.desactiverMembre en 8 étapes transactionnelles :
1. GARDE-FOU mono-admin : refuse 409 Conflict si le membre est le seul
ORGADMIN d'au moins une org (évite l'orphelinage).
2. DB : actif=false + statutCompte='DESACTIVE'.
3. Adhésions actives → SUSPENDU + décrément nombreMembres.
4. MembreRole (ORGADMIN, TRESORIER...) → actif=false, dateFin=today.
5. Notifications pending (EN_ATTENTE, ECHEC_TEMPORAIRE) → ANNULEE.
6. Keycloak (lions-user-manager) : user.enabled=false → login bloqué.
7. Kafka : publishMemberDeactivated(membre) sur unionflow.members.events
→ consumers peuvent réagir (comptes épargne, inscriptions, approvals, etc.)
8. AuditLog MEMBRE_DESACTIVE : opérateur, timestamp, compteurs (RGPD/compliance).
Côté liste :
- listerMembres/compterMembres : filtre actif=true par défaut (SuperAdmin).
- MembreRepository.findDistinctByOrganisationIdIn : idem pour OrgAdmin.
Services ajoutés :
- AuditService.logMembreDesactive
- KafkaEventProducer.publishMemberDeactivated
2026-04-15 20:12:55 +00:00
dahoud
4816d1ac50
feat(security): ownership + protection anti-admin sur lifecycle membres
...
verifierOwnershipEtProtectionAdmin() appelé sur les 5 endpoints lifecycle
(radier-adhesion, archiver-adhesion, activer/suspendre/radier par membre):
1. Ownership: un ADMIN_ORGANISATION ne peut agir que sur les membres des
organisations dont il est responsable (sinon 403).
2. Anti-admin: un ADMIN_ORGANISATION ne peut pas agir sur un autre ORGADMIN
ou SUPERADMIN (sinon 403).
3. SUPER_ADMIN/ADMIN passent directement (accès total).
Comble les failles SEC-01/SEC-02 de l'audit technique.
2026-04-15 20:12:37 +00:00
dahoud
78d8fd7cd8
feat(sync): MembreRoleSyncService + count admins dynamique
...
- Nouveau MembreRoleSyncService.ensureOrgAdminRole : auto-crée un MembreRole ORGADMIN
quand un user avec rôle Keycloak ADMIN_ORGANISATION se connecte sans entrée DB
(couvre les comptes créés directement dans Keycloak).
- OrganisationContextFilter appelle syncService.ensureOrgAdminRole quand le rôle
Keycloak est présent mais MembreRole absent (non bloquant sur erreur).
- MembreRoleRepository.countAdminsByOrganisationId : count strict (ORGADMIN + actif
+ dateDebut/dateFin valides) avec fallback sur codes alternatifs si strict=0.
- OrganisationService.convertToResponse : nombreAdministrateurs dynamique via
MembreRoleRepository (remplace le champ Organisation jamais mis à jour).
2026-04-15 20:05:49 +00:00
dahoud
e81c75b828
fix(db): V30/V31 aligner membres_roles avec entité + rendre colonnes notifications legacy nullables
...
- V30: ajoute membre_organisation_id/organisation_id/date_debut/fin/commentaire si absents,
rend membre_id nullable (legacy V1), remplace uk_membre_role par uk_mr_membre_org_role,
ajoute indexes. Idempotent via DO blocks.
- V31: rend destinataire_id, titre, nombre_tentatives nullables dans notifications
(colonnes legacy V1 que l'entité n'utilise plus, bloquaient les INSERT).
2026-04-15 20:05:36 +00:00
dahoud
6bd3f6bc18
fix(admin): supprimer @Provider de JwtPropagationFilter
...
@Provider enregistre le filtre GLOBALEMENT sur tous les REST clients.
JwtPropagationFilter s'exécutait après AdminServiceTokenHeadersFactory et
écrasait le token de service account avec le JWT utilisateur mobile
→ LUM recevait un token du realm unionflow (kid inconnu) → 401.
La propagation JWT est déjà gérée par OidcTokenPropagationHeadersFactory
sur UserServiceClient/RoleServiceClient via @RegisterClientHeaders.
JwtPropagationFilter est conservé sans @Provider pour référence future.
2026-04-12 15:27:38 +00:00
dahoud
e107d2ecce
fix(admin): AdminUserService doit utiliser AdminUserServiceClient et AdminRoleServiceClient
...
Le service admin injectait UserServiceClient/RoleServiceClient (propagation du token
utilisateur unionflow) au lieu des clients Admin dédiés (service account lions-user-manager).
Résultat : le token JWT de l'utilisateur mobile était envoyé à LUM → 401 car LUM ne
connaît pas les clés du realm unionflow.
Correctif :
- AdminUserService -> AdminUserServiceClient + AdminRoleServiceClient (service account)
- UserServiceClient + RoleServiceClient remis à OidcTokenPropagationHeadersFactory
(ces clients non-admin propagent le token utilisateur pour des usages futurs)
2026-04-12 15:22:09 +00:00
dahoud
0b79a2ee68
fix(admin): utiliser AdminServiceTokenHeadersFactory pour UserServiceClient et RoleServiceClient
...
Les appels vers lions-user-manager nécessitent un token du realm lions-user-manager
(service account). OidcTokenPropagationHeadersFactory transmettait le token utilisateur
du realm unionflow → 401 systématique. AdminServiceTokenHeadersFactory injecte le bon
token via l'OIDC client admin-service.
2026-04-12 15:00:34 +00:00
dahoud
dfd883b27c
fix(dev): corriger config OIDC issuer + supprimer tables fantômes pré-consolidation
...
- application-dev.properties : token.issuer utilise ${DEV_HOST:localhost} pour valider
les JWT émis par Keycloak via l'IP LAN (mobile physique sur réseau local)
- .env : DEV_HOST=192.168.1.13 — source unique côté backend, en sync avec
android/local.properties → dev.host côté mobile
- V24 : suppression des 8 tables fantômes issues des migrations pré-consolidation
(document, permission, favori, ticket, suggestion, suggestion_vote, configuration,
role_permission) — toutes vides, les entités JPA pointaient déjà vers les tables
plurielles correctes. Les contraintes uk_role_permission et uk_suggestion_vote
sont maintenant sur les vraies tables (roles_permissions, suggestion_votes).
2026-04-12 14:51:22 +00:00
dahoud
f7f0a65f56
fix: correct builder method names for boolean fields in response DTOs
...
ConversationResponse/MessageResponse fields (muted, pinned, archived,
edited, deleted) are primitive booleans — Lombok @Builder generates
.muted() not .isMuted(). Also use Boolean.TRUE.equals() for null-safe
unboxing from entity Boolean wrapper fields.
2026-04-11 03:01:28 +00:00
dahoud
272525aa4d
fix(tests): replace record-style accessors with JavaBean getters
...
MembreDashboardServiceTest and OrganisationServiceTest used record
accessor syntax (e.g. result.nom()) on DTO classes that were converted
from records to @Data classes — now using getters (result.getNom()).
2026-04-11 02:07:33 +00:00
dahoud
1a5138b87a
fix(impl): version parent 1.0.4 + server-api 1.0.4
2026-04-11 02:01:05 +00:00
dahoud
31e8d5534c
fix: kafka dev standalone, OIDC realm LUM, Flyway out-of-order, shutdown guard, TypeRef categorie/modules
...
- docker-compose.dev.yml: retire le service kafka (standalone existant sur :9092), kafka-ui pointe host.docker.internal:9092
- application-dev.properties: OIDC admin-service realm corrigé lions-user-manager (fix AUTH changement mdp)
- application-prod.properties: nouvelle var KEYCLOAK_LUM_AUTH_SERVER_URL + fallback KEYCLOAK_CLIENT_SECRET
- application.properties: quarkus.flyway.out-of-order=true (évite échec si migration hors-séquence)
- V10 renommé V10_1 (évite conflit avec historique Flyway existant)
- AlertMonitoringService: guard Arc.container().isRunning() pour éviter NPE au shutdown
- TypeOrganisationReferenceResource: forward categorie + modulesRequis au service
- Tests: coverage TypeOrganisationReferenceResource + TypeReferenceService
2026-04-11 01:25:45 +00:00