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

@@ -4,10 +4,13 @@ library create_demande_aide_dialog;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import '../../../../core/utils/logger.dart';
import '../../bloc/solidarity_bloc.dart';
import '../../data/models/demande_aide_model.dart';
import '../../../organizations/data/repositories/organization_repository.dart';
import '../../../organizations/domain/repositories/organization_repository.dart';
import '../../../organizations/data/models/organization_model.dart';
import '../../../members/data/models/membre_complete_model.dart';
import '../../../profile/domain/repositories/profile_repository.dart';
class CreateDemandeAideDialog extends StatefulWidget {
final VoidCallback onCreated;
@@ -28,6 +31,8 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
String? _type;
List<OrganizationModel> _organisations = [];
bool _loading = false;
bool _isInitLoading = true;
MembreCompletModel? _me;
static const List<Map<String, String>> _types = [
{'value': 'FINANCIERE', 'label': 'Financière'},
@@ -42,7 +47,32 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
@override
void initState() {
super.initState();
_loadOrgs();
_loadInitialData();
}
Future<void> _loadInitialData() async {
try {
final user = await GetIt.instance<IProfileRepository>().getMe();
final orgRepo = GetIt.instance<IOrganizationRepository>();
final list = await orgRepo.getOrganizations(page: 0, size: 100);
if (mounted) {
setState(() {
_me = user;
_organisations = list;
_isInitLoading = false;
});
}
} catch (e, st) {
AppLogger.error('CreateDemandeAideDialog: chargement données initiales échoué', error: e, stackTrace: st);
if (mounted) {
setState(() {
_isInitLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Impossible de charger le profil ou les organisations. Réessayez.')),
);
}
}
}
@override
@@ -54,17 +84,13 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
super.dispose();
}
Future<void> _loadOrgs() async {
try {
final repo = GetIt.instance<OrganizationRepository>();
final list = await repo.getOrganizations(page: 0, size: 100);
if (mounted) setState(() => _organisations = list);
} catch (_) {
if (mounted) setState(() {});
}
}
void _submit() {
if (_me == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profil non chargé, veuillez réessayer')),
);
return;
}
if (!_formKey.currentState!.validate()) return;
final titre = _titreController.text.trim();
final description = _descriptionController.text.trim();
@@ -85,6 +111,7 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
type: _type,
montantDemande: montant,
organisationId: _organisationId,
demandeurId: _me!.id,
dateDemande: DateTime.now(),
statut: 'BROUILLON',
);
@@ -106,6 +133,21 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (_isInitLoading)
const CircularProgressIndicator()
else if (_me != null)
TextFormField(
initialValue: '${_me!.prenom} ${_me!.nom}',
decoration: const InputDecoration(
labelText: 'Demandeur',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.person),
),
enabled: false,
)
else
const Text('Impossible de récupérer votre profil', style: TextStyle(color: Colors.red)),
const SizedBox(height: 12),
TextFormField(
controller: _titreController,
decoration: const InputDecoration(
@@ -164,6 +206,7 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
const SizedBox(height: 12),
DropdownButtonFormField<String>(
value: _organisationId,
isExpanded: true,
decoration: const InputDecoration(
labelText: 'Organisation',
border: OutlineInputBorder(),
@@ -171,7 +214,7 @@ class _CreateDemandeAideDialogState extends State<CreateDemandeAideDialog> {
items: _organisations
.map((o) => DropdownMenuItem(
value: o.id,
child: Text(o.nom),
child: Text(o.nom, overflow: TextOverflow.ellipsis, maxLines: 1),
))
.toList(),
onChanged: _loading ? null : (v) => setState(() => _organisationId = v),