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,38 @@
import 'package:dio/dio.dart';
import 'package:injectable/injectable.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/network/api_client.dart';
import '../../../../core/utils/logger.dart';
import '../models/compliance_snapshot_model.dart';
abstract class ComplianceRemoteDataSource {
/// GET /api/compliance/dashboard — snapshot pour l'organisation active.
Future<ComplianceSnapshotModel> getSnapshotCurrent();
}
@Injectable(as: ComplianceRemoteDataSource)
class ComplianceRemoteDataSourceImpl implements ComplianceRemoteDataSource {
final ApiClient apiClient;
ComplianceRemoteDataSourceImpl(this.apiClient);
@override
Future<ComplianceSnapshotModel> getSnapshotCurrent() async {
try {
final response = await apiClient.get('/api/compliance/dashboard');
if (response.statusCode == 200 && response.data is Map<String, dynamic>) {
return ComplianceSnapshotModel.fromJson(
response.data as Map<String, dynamic>,
);
}
throw ServerException('Compliance snapshot HTTP ${response.statusCode}');
} on DioException catch (e) {
AppLogger.error('ComplianceRemoteDataSource: getSnapshotCurrent', error: e);
rethrow;
} catch (e, st) {
AppLogger.error('ComplianceRemoteDataSource: getSnapshotCurrent',
error: e, stackTrace: st);
rethrow;
}
}
}