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,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../domain/entities/devise.dart';
const _kDevisePrefereeKey = 'unionflow.devise.preferee';
/// Widget de sélection de la devise préférée (persistée en secure storage).
///
/// Utilisé sur la page Profil/Paramètres. Notifie via [onChanged] avec la
/// nouvelle devise quand l'utilisateur fait un choix.
class DeviseSelector extends StatefulWidget {
final Devise? initial;
final ValueChanged<Devise>? onChanged;
const DeviseSelector({super.key, this.initial, this.onChanged});
/// Lit la devise préférée du secure storage. Fallback sur XOF.
static Future<Devise> readPreferred(FlutterSecureStorage storage) async {
try {
final v = await storage.read(key: _kDevisePrefereeKey);
return Devise.fromCode(v);
} catch (_) {
return Devise.reference();
}
}
/// Persiste la devise préférée.
static Future<void> writePreferred(
FlutterSecureStorage storage, Devise devise) async {
await storage.write(key: _kDevisePrefereeKey, value: devise.code);
}
@override
State<DeviseSelector> createState() => _DeviseSelectorState();
}
class _DeviseSelectorState extends State<DeviseSelector> {
late Devise _selected;
@override
void initState() {
super.initState();
_selected = widget.initial ?? Devise.reference();
}
@override
Widget build(BuildContext context) {
return DropdownButtonFormField<Devise>(
value: _selected,
decoration: const InputDecoration(
labelText: 'Devise préférée',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.account_balance_wallet_outlined),
),
items: Devise.values
.map(
(d) => DropdownMenuItem<Devise>(
value: d,
child: Text('${d.code}${d.libelle}'),
),
)
.toList(),
onChanged: (d) {
if (d == null) return;
setState(() => _selected = d);
widget.onChanged?.call(d);
},
);
}
}