feat: WebSocket temps réel + Finance Workflow + corrections

- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics)
  * Backend: KafkaEventProducer, KafkaEventConsumer
  * Mobile: WebSocketService (reconnection, heartbeat, typed events)
  * DashboardBloc: Auto-refresh depuis WebSocket events

- Finance Workflow: approbations + budgets (backend + mobile)
  * Backend: entities, services, resources, migrations Flyway V6
  * Mobile: features finance_workflow complète avec BLoC

- Corrections DI: interfaces IRepository partout
  * IProfileRepository, IOrganizationRepository, IMembreRepository
  * GetIt configuré avec @injectable

- Spec-Kit: constitution + templates mis à jour
  * .specify/memory/constitution.md enrichie
  * Templates agent, plan, spec, tasks, checklist

- Nettoyage: fichiers temporaires supprimés

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -0,0 +1,72 @@
/// Modèle pour le "compte adhérent" unifié (GET /api/membres/mon-compte).
class CompteAdherentModel {
final String numeroMembre;
final String nomComplet;
final String? organisationNom;
final String? dateAdhesion;
final String statutCompte;
final double soldeCotisations;
final double soldeEpargne;
final double soldeBloque;
final double soldeTotalDisponible;
final double encoursCreditTotal;
final double capaciteEmprunt;
final int nombreCotisationsPayees;
final int nombreCotisationsTotal;
final int nombreCotisationsEnRetard;
final int? tauxEngagement;
final int nombreComptesEpargne;
final String dateCalcul;
const CompteAdherentModel({
required this.numeroMembre,
required this.nomComplet,
this.organisationNom,
this.dateAdhesion,
this.statutCompte = 'ACTIF',
this.soldeCotisations = 0,
this.soldeEpargne = 0,
this.soldeBloque = 0,
this.soldeTotalDisponible = 0,
this.encoursCreditTotal = 0,
this.capaciteEmprunt = 0,
this.nombreCotisationsPayees = 0,
this.nombreCotisationsTotal = 0,
this.nombreCotisationsEnRetard = 0,
this.tauxEngagement,
this.nombreComptesEpargne = 0,
required this.dateCalcul,
});
factory CompteAdherentModel.fromJson(Map<String, dynamic> json) {
return CompteAdherentModel(
numeroMembre: json['numeroMembre'] as String? ?? 'N/A',
nomComplet: json['nomComplet'] as String? ?? '',
organisationNom: json['organisationNom'] as String?,
dateAdhesion: json['dateAdhesion'] as String?,
statutCompte: json['statutCompte'] as String? ?? 'ACTIF',
soldeCotisations: _toDouble(json['soldeCotisations']),
soldeEpargne: _toDouble(json['soldeEpargne']),
soldeBloque: _toDouble(json['soldeBloque']),
soldeTotalDisponible: _toDouble(json['soldeTotalDisponible']),
encoursCreditTotal: _toDouble(json['encoursCreditTotal']),
capaciteEmprunt: _toDouble(json['capaciteEmprunt']),
nombreCotisationsPayees: (json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
nombreCotisationsTotal: (json['nombreCotisationsTotal'] as num?)?.toInt() ?? 0,
nombreCotisationsEnRetard: (json['nombreCotisationsEnRetard'] as num?)?.toInt() ?? 0,
tauxEngagement: (json['tauxEngagement'] as num?)?.toInt(),
nombreComptesEpargne: (json['nombreComptesEpargne'] as num?)?.toInt() ?? 0,
dateCalcul: json['dateCalcul'] as String? ?? '',
);
}
static double _toDouble(dynamic v) {
if (v == null) return 0;
if (v is num) return v.toDouble();
if (v is String) return double.tryParse(v) ?? 0;
return 0;
}
}

View File

@@ -17,6 +17,8 @@ class DashboardStatsModel extends Equatable {
final double monthlyGrowth;
final double engagementRate;
final DateTime lastUpdated;
final int? totalOrganizations;
final Map<String, int>? organizationTypeDistribution;
const DashboardStatsModel({
required this.totalMembers,
@@ -30,6 +32,8 @@ class DashboardStatsModel extends Equatable {
required this.monthlyGrowth,
required this.engagementRate,
required this.lastUpdated,
this.totalOrganizations,
this.organizationTypeDistribution,
});
factory DashboardStatsModel.fromJson(Map<String, dynamic> json) =>
@@ -63,6 +67,8 @@ class DashboardStatsModel extends Equatable {
monthlyGrowth,
engagementRate,
lastUpdated,
totalOrganizations,
organizationTypeDistribution,
];
}

View File

@@ -20,6 +20,11 @@ DashboardStatsModel _$DashboardStatsModelFromJson(Map<String, dynamic> json) =>
monthlyGrowth: (json['monthlyGrowth'] as num).toDouble(),
engagementRate: (json['engagementRate'] as num).toDouble(),
lastUpdated: DateTime.parse(json['lastUpdated'] as String),
totalOrganizations: (json['totalOrganizations'] as num?)?.toInt(),
organizationTypeDistribution:
(json['organizationTypeDistribution'] as Map<String, dynamic>?)?.map(
(k, e) => MapEntry(k, (e as num).toInt()),
),
);
Map<String, dynamic> _$DashboardStatsModelToJson(
@@ -36,6 +41,8 @@ Map<String, dynamic> _$DashboardStatsModelToJson(
'monthlyGrowth': instance.monthlyGrowth,
'engagementRate': instance.engagementRate,
'lastUpdated': instance.lastUpdated.toIso8601String(),
'totalOrganizations': instance.totalOrganizations,
'organizationTypeDistribution': instance.organizationTypeDistribution,
};
RecentActivityModel _$RecentActivityModelFromJson(Map<String, dynamic> json) =>

View File

@@ -11,6 +11,8 @@ class MembreDashboardSyntheseModel {
final double totalCotisationsPayeesToutTemps;
/// Nombre de cotisations payées (pour carte « Cotisations »).
final int nombreCotisationsPayees;
/// Nombre total de cotisations (toutes années, tous statuts).
final int nombreCotisationsTotal;
final String statutCotisations;
final int? tauxCotisationsPerso;
final double monSoldeEpargne;
@@ -32,6 +34,7 @@ class MembreDashboardSyntheseModel {
this.totalCotisationsPayeesAnnee = 0,
this.totalCotisationsPayeesToutTemps = 0,
this.nombreCotisationsPayees = 0,
this.nombreCotisationsTotal = 0,
this.statutCotisations = 'À jour',
this.tauxCotisationsPerso,
this.monSoldeEpargne = 0,
@@ -55,6 +58,8 @@ class MembreDashboardSyntheseModel {
totalCotisationsPayeesAnnee: _toDouble(json['totalCotisationsPayeesAnnee']),
totalCotisationsPayeesToutTemps: _toDouble(json['totalCotisationsPayeesToutTemps']),
nombreCotisationsPayees: (json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
nombreCotisationsTotal: (json['nombreCotisationsTotal'] as num?)?.toInt() ??
(json['nombreCotisationsPayees'] as num?)?.toInt() ?? 0,
statutCotisations: json['statutCotisations'] as String? ?? 'À jour',
tauxCotisationsPerso: (json['tauxCotisationsPerso'] as num?)?.toInt(),
monSoldeEpargne: _toDouble(json['monSoldeEpargne']),
@@ -70,6 +75,7 @@ class MembreDashboardSyntheseModel {
);
}
static double _toDouble(dynamic v) {
if (v == null) return 0;
if (v is num) return v.toDouble();