feat(sprint-9 mobile 2026-04-25): feature compliance dashboard + Devise enum + selector + tests

Apporte aux compliance officers et controleurs internes l'accès mobile au tableau de bord
de conformité backend (P1-NEW-7). Et prépare la diaspora avec Devise enum + sélecteur
préférence persisté.

Feature Compliance (Clean Architecture)
- Domain : ComplianceSnapshot + ConformiteIndicateur (Equatable), helpers scoreSeverite + hasAlertesCritiques
- Data : ComplianceSnapshotModel.fromJson (parsing tolerant aux nullables), ComplianceRemoteDataSourceImpl Dio (GET /api/compliance/dashboard), ComplianceRepositoryImpl @Injectable
- Presentation : ComplianceBloc (Load/Refresh events, Initial/Loading/Loaded/Error states), ConformiteDashboardPage (Material 3, ScoreCard 0-100 colorée, 9 IndicateurTile, AlertesCard rouge si critiques)

Feature Devise
- Devise enum (10 valeurs miroirs backend, code/libelle/zone)
- fromCode tolérant casse + null/vide → XOF
- estInternationale pour AML
- DeviseSelector widget DropdownButtonFormField + readPreferred/writePreferred via FlutterSecureStorage (clé unionflow.devise.preferee)

Tests (17/17 verts)
- ComplianceSnapshot : 7 tests (scoreSeverite × 3, hasAlertesCritiques × 4)
- ComplianceSnapshotModel.fromJson : 4 tests (complet, fallbacks, string→double, indicateur invalide)
- Devise enum : 6 tests (reference, fromCode parse, fromCode null/inconnu, estInternationale × 2, intégrité valeurs)

Note : feature reporting trimestriel mobile (PDF viewer + bloc liste) reportée à un sprint
dédié — nécessite intégration pdf viewer + cache local non triviale.
This commit is contained in:
dahoud
2026-04-25 11:11:03 +00:00
parent 8356ccc0b0
commit 8c1a254e80
12 changed files with 808 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:unionflow_mobile_apps/features/compliance/data/models/compliance_snapshot_model.dart';
void main() {
group('ComplianceSnapshotModel.fromJson', () {
test('parsing complet', () {
final json = {
'organisationId': 'org-uuid',
'organisationNom': 'Mutuelle Test',
'referentielComptable': 'SYCEBNL',
'complianceOfficerDesigne': true,
'agAnnuelle': {'statut': 'OK', 'message': 'AG tenue'},
'rapportAirms': {'statut': 'EN_ATTENTE', 'message': ''},
'dirigeantsAvecCmu': 3,
'tauxKycAJourPct': 85.5,
'tauxFormationLbcFtPct': 70.0,
'commissaireAuxComptes': {'statut': 'OBLIGATOIRE', 'message': 'SFD'},
'fomusCi': {'statut': 'EN_VEILLE', 'message': ''},
'couvertureUboPct': 60.0,
'scoreGlobal': 82,
};
final m = ComplianceSnapshotModel.fromJson(json);
expect(m.organisationNom, 'Mutuelle Test');
expect(m.referentielComptable, 'SYCEBNL');
expect(m.complianceOfficerDesigne, isTrue);
expect(m.agAnnuelle.statut, 'OK');
expect(m.dirigeantsAvecCmu, 3);
expect(m.tauxKycAJourPct, 85.5);
expect(m.scoreGlobal, 82);
});
test('valeurs manquantes → fallbacks raisonnables', () {
final m = ComplianceSnapshotModel.fromJson(<String, dynamic>{});
expect(m.organisationId, '');
expect(m.referentielComptable, 'SYSCOHADA');
expect(m.complianceOfficerDesigne, isFalse);
expect(m.agAnnuelle.statut, 'EN_VEILLE');
expect(m.dirigeantsAvecCmu, 0);
expect(m.tauxKycAJourPct, 0.0);
expect(m.scoreGlobal, 0);
});
test('taux passé en string → converti en double', () {
final m = ComplianceSnapshotModel.fromJson({
'tauxKycAJourPct': '85.5',
'couvertureUboPct': '60',
});
expect(m.tauxKycAJourPct, 85.5);
expect(m.couvertureUboPct, 60.0);
});
test('indicateur non-Map → fallback EN_VEILLE', () {
final m = ComplianceSnapshotModel.fromJson({'agAnnuelle': 'invalid'});
expect(m.agAnnuelle.statut, 'EN_VEILLE');
});
});
}

View File

@@ -0,0 +1,56 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:unionflow_mobile_apps/features/compliance/domain/entities/compliance_snapshot.dart';
ComplianceSnapshot _build({
int score = 85,
bool officer = true,
String agStatut = 'OK',
}) {
return ComplianceSnapshot(
organisationId: 'org-1',
organisationNom: 'Test',
referentielComptable: 'SYSCOHADA',
complianceOfficerDesigne: officer,
agAnnuelle: ConformiteIndicateur(statut: agStatut, message: ''),
rapportAirms: const ConformiteIndicateur(statut: 'OK', message: ''),
dirigeantsAvecCmu: 3,
tauxKycAJourPct: 80,
tauxFormationLbcFtPct: 70,
commissaireAuxComptes: const ConformiteIndicateur(statut: 'OPTIONNEL', message: ''),
fomusCi: const ConformiteIndicateur(statut: 'EN_VEILLE', message: ''),
couvertureUboPct: 60,
scoreGlobal: score,
);
}
void main() {
group('ComplianceSnapshot', () {
test('scoreSeverite — score >=80 → success', () {
expect(_build(score: 85).scoreSeverite, 'success');
});
test('scoreSeverite — 60..79 → warning', () {
expect(_build(score: 70).scoreSeverite, 'warning');
});
test('scoreSeverite — <60 → danger', () {
expect(_build(score: 50).scoreSeverite, 'danger');
});
test('hasAlertesCritiques — score 85 + officer + AG OK → false', () {
expect(_build().hasAlertesCritiques, isFalse);
});
test('hasAlertesCritiques — officer absent → true', () {
expect(_build(officer: false).hasAlertesCritiques, isTrue);
});
test('hasAlertesCritiques — AG en retard → true', () {
expect(_build(agStatut: 'RETARD').hasAlertesCritiques, isTrue);
});
test('hasAlertesCritiques — score <60 → true', () {
expect(_build(score: 50).hasAlertesCritiques, isTrue);
});
});
}