feat(unionflow): ajout Spec-Kit, constitution, mission mutuelles

- Config Spec-Kit pour Spec-Driven Development
- CONSTITUTION.md + .specify/memory/constitution.md
- Commandes Cursor /speckit.*, règles projet
- Mission: associations + mutuelles d'épargne et de financement
- .gitignore: versionner config spec-kit unionflow

Made-with: Cursor
This commit is contained in:
dahoud
2026-02-27 14:41:07 +00:00
parent 144b68f8e7
commit b1957c1c81
631 changed files with 104070 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
/// Repository pour la gestion des cotisations via l'API backend
library contribution_repository;
import 'package:dio/dio.dart';
import '../models/contribution_model.dart';
/// Repository des cotisations - appels API réels vers /api/cotisations
class ContributionRepository {
final Dio _dio;
static const String _baseUrl = '/api/cotisations';
ContributionRepository(this._dio);
/// Récupère la liste des cotisations avec pagination
Future<ContributionPageResult> getCotisations({
int page = 0,
int size = 20,
String? membreId,
String? statut,
String? type,
int? annee,
}) async {
final queryParams = <String, dynamic>{
'page': page,
'size': size,
};
if (membreId != null) queryParams['membreId'] = membreId;
if (statut != null) queryParams['statut'] = statut;
if (type != null) queryParams['type'] = type;
if (annee != null) queryParams['annee'] = annee;
final response = await _dio.get(
_baseUrl,
queryParameters: queryParams,
);
if (response.statusCode == 200) {
final data = response.data;
if (data is List) {
final contributions = data
.map((json) => ContributionModel.fromJson(json as Map<String, dynamic>))
.toList();
return ContributionPageResult(
contributions: contributions,
total: contributions.length,
page: page,
size: size,
totalPages: 1,
);
} else if (data is Map<String, dynamic>) {
final List<dynamic> content = data['content'] ?? data['items'] ?? [];
final contributions = content
.map((json) => ContributionModel.fromJson(json as Map<String, dynamic>))
.toList();
return ContributionPageResult(
contributions: contributions,
total: data['totalElements'] ?? data['total'] ?? contributions.length,
page: data['number'] ?? page,
size: data['size'] ?? size,
totalPages: data['totalPages'] ?? 1,
);
}
}
throw Exception('Erreur lors de la récupération des cotisations: ${response.statusCode}');
}
/// Récupère une cotisation par ID
Future<ContributionModel> getCotisationById(String id) async {
final response = await _dio.get('$_baseUrl/$id');
if (response.statusCode == 200) {
return ContributionModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Cotisation non trouvée');
}
/// Crée une nouvelle cotisation
Future<ContributionModel> createCotisation(ContributionModel contribution) async {
final response = await _dio.post(_baseUrl, data: contribution.toJson());
if (response.statusCode == 201 || response.statusCode == 200) {
return ContributionModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur lors de la création: ${response.statusCode}');
}
/// Met à jour une cotisation
Future<ContributionModel> updateCotisation(String id, ContributionModel contribution) async {
final response = await _dio.put('$_baseUrl/$id', data: contribution.toJson());
if (response.statusCode == 200) {
return ContributionModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur lors de la mise à jour: ${response.statusCode}');
}
/// Supprime une cotisation
Future<void> deleteCotisation(String id) async {
final response = await _dio.delete('$_baseUrl/$id');
if (response.statusCode != 200 && response.statusCode != 204) {
throw Exception('Erreur lors de la suppression: ${response.statusCode}');
}
}
/// Enregistre un paiement
Future<ContributionModel> enregistrerPaiement(
String cotisationId, {
required double montant,
required DateTime datePaiement,
required String methodePaiement,
String? numeroPaiement,
String? referencePaiement,
}) async {
final response = await _dio.post(
'$_baseUrl/$cotisationId/paiement',
data: {
'montant': montant,
'datePaiement': datePaiement.toIso8601String(),
'methodePaiement': methodePaiement,
if (numeroPaiement != null) 'numeroPaiement': numeroPaiement,
if (referencePaiement != null) 'referencePaiement': referencePaiement,
},
);
if (response.statusCode == 200) {
return ContributionModel.fromJson(response.data as Map<String, dynamic>);
}
throw Exception('Erreur lors de l\'enregistrement du paiement: ${response.statusCode}');
}
/// Récupère les statistiques des cotisations
Future<Map<String, dynamic>> getStatistiques() async {
final response = await _dio.get('$_baseUrl/statistiques');
if (response.statusCode == 200) {
return response.data as Map<String, dynamic>;
}
throw Exception('Erreur lors de la récupération des statistiques');
}
/// Envoie un rappel de paiement
Future<void> envoyerRappel(String cotisationId) async {
final response = await _dio.post('$_baseUrl/$cotisationId/rappel');
if (response.statusCode != 200) {
throw Exception('Erreur lors de l\'envoi du rappel');
}
}
/// Génère les cotisations annuelles
Future<int> genererCotisationsAnnuelles(int annee) async {
final response = await _dio.post(
'$_baseUrl/generer',
data: {'annee': annee},
);
if (response.statusCode == 200) {
return response.data['nombreGenere'] ?? 0;
}
throw Exception('Erreur lors de la génération');
}
}
/// Résultat paginé de cotisations
class ContributionPageResult {
final List<ContributionModel> contributions;
final int total;
final int page;
final int size;
final int totalPages;
const ContributionPageResult({
required this.contributions,
required this.total,
required this.page,
required this.size,
required this.totalPages,
});
}