feat(sprint-13.C mobile 2026-04-25): feature reporting (rapports trimestriels Contrôleur Interne)
Mobile mirror du backend P2-NEW-3. DRY strict — Clean Architecture identique aux autres features (compliance, devise). Domain - StatutRapport enum (draft, signe, archive, inconnu) + extension StatutRapportX (code/libelle/severite/fromCode) - RapportTrimestriel entity (Equatable) + helpers libellePeriode, scoreSeverite (>=80 success, >=60 warning, <60 danger) Data - RapportTrimestrielModel.fromJson (parsing tolérant null + dates ISO) - ReportingRemoteDataSourceImpl (Dio) : lister/generer/signer/archiver/telechargerPdf - ReportingRepositoryImpl @Injectable Presentation - ReportingBloc : Load/Generer/Signer/Archiver events, 4 states (Initial/Loading/Loaded/Error) - RapportsTrimestrielsPage : Material 3 ListView avec cards (period, score coloré, tag statut, hash tronqué) + EmptyView + ErrorView Tests (10/10 verts) - StatutRapportX × 2 (fromCode parse/null/inconnu, libelle/severite/code) - RapportTrimestriel × 5 (libellePeriode, estDraft/estSigne/estArchive, scoreSeverite × 3) - RapportTrimestrielModel.fromJson × 3 (complet, fallbacks, date invalide) Note : viewer PDF interne reporté à un sprint dédié (intégration pdfx + permission storage Android). Téléchargement bytes via API exposé dans datasource pour usage futur.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
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/rapport_trimestriel_model.dart';
|
||||
|
||||
abstract class ReportingRemoteDataSource {
|
||||
Future<List<RapportTrimestrielModel>> lister({String? orgId, int? annee});
|
||||
Future<RapportTrimestrielModel> generer({String? orgId, required int annee, required int trimestre});
|
||||
Future<RapportTrimestrielModel> signer(String id, String signataireId);
|
||||
Future<RapportTrimestrielModel> archiver(String id);
|
||||
Future<List<int>> telechargerPdf(String id);
|
||||
}
|
||||
|
||||
@Injectable(as: ReportingRemoteDataSource)
|
||||
class ReportingRemoteDataSourceImpl implements ReportingRemoteDataSource {
|
||||
final ApiClient apiClient;
|
||||
ReportingRemoteDataSourceImpl(this.apiClient);
|
||||
|
||||
static const _basePath = '/api/rapports/trimestriel';
|
||||
|
||||
@override
|
||||
Future<List<RapportTrimestrielModel>> lister({String? orgId, int? annee}) async {
|
||||
try {
|
||||
final response = await apiClient.get(
|
||||
_basePath,
|
||||
queryParameters: {
|
||||
if (orgId != null) 'orgId': orgId,
|
||||
if (annee != null) 'annee': annee,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is List) {
|
||||
return (response.data as List)
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(RapportTrimestrielModel.fromJson)
|
||||
.toList();
|
||||
}
|
||||
throw ServerException('Liste rapports HTTP ${response.statusCode}');
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Reporting: lister', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<RapportTrimestrielModel> generer({String? orgId, required int annee, required int trimestre}) async {
|
||||
try {
|
||||
final response = await apiClient.post(
|
||||
'$_basePath/generer',
|
||||
queryParameters: {
|
||||
if (orgId != null) 'orgId': orgId,
|
||||
'annee': annee,
|
||||
'trimestre': trimestre,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is Map<String, dynamic>) {
|
||||
return RapportTrimestrielModel.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Generer rapport HTTP ${response.statusCode}');
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Reporting: generer', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<RapportTrimestrielModel> signer(String id, String signataireId) async {
|
||||
try {
|
||||
final response = await apiClient.post(
|
||||
'$_basePath/$id/signer',
|
||||
queryParameters: {'signataireId': signataireId},
|
||||
);
|
||||
if (response.statusCode == 200 && response.data is Map<String, dynamic>) {
|
||||
return RapportTrimestrielModel.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Signer rapport HTTP ${response.statusCode}');
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Reporting: signer', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<RapportTrimestrielModel> archiver(String id) async {
|
||||
try {
|
||||
final response = await apiClient.post('$_basePath/$id/archiver');
|
||||
if (response.statusCode == 200 && response.data is Map<String, dynamic>) {
|
||||
return RapportTrimestrielModel.fromJson(response.data as Map<String, dynamic>);
|
||||
}
|
||||
throw ServerException('Archiver rapport HTTP ${response.statusCode}');
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Reporting: archiver', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<int>> telechargerPdf(String id) async {
|
||||
try {
|
||||
final response = await apiClient.get<List<int>>(
|
||||
'$_basePath/$id/pdf',
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
return response.data!;
|
||||
}
|
||||
throw ServerException('Telecharger PDF HTTP ${response.statusCode}');
|
||||
} on DioException catch (e) {
|
||||
AppLogger.error('Reporting: telechargerPdf', error: e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user