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:
129
lib/features/reporting/presentation/bloc/reporting_bloc.dart
Normal file
129
lib/features/reporting/presentation/bloc/reporting_bloc.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import '../../../../core/utils/logger.dart';
|
||||
import '../../domain/entities/rapport_trimestriel.dart';
|
||||
import '../../domain/repositories/reporting_repository.dart';
|
||||
|
||||
// ── Events ───────────────────────────────────────────────────────────
|
||||
abstract class ReportingEvent extends Equatable {
|
||||
const ReportingEvent();
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadRapports extends ReportingEvent {
|
||||
final int? annee;
|
||||
const LoadRapports({this.annee});
|
||||
@override
|
||||
List<Object?> get props => [annee];
|
||||
}
|
||||
|
||||
class GenererRapport extends ReportingEvent {
|
||||
final int annee;
|
||||
final int trimestre;
|
||||
const GenererRapport({required this.annee, required this.trimestre});
|
||||
@override
|
||||
List<Object?> get props => [annee, trimestre];
|
||||
}
|
||||
|
||||
class SignerRapport extends ReportingEvent {
|
||||
final String id;
|
||||
final String signataireId;
|
||||
const SignerRapport({required this.id, required this.signataireId});
|
||||
@override
|
||||
List<Object?> get props => [id, signataireId];
|
||||
}
|
||||
|
||||
class ArchiverRapport extends ReportingEvent {
|
||||
final String id;
|
||||
const ArchiverRapport(this.id);
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
// ── States ───────────────────────────────────────────────────────────
|
||||
abstract class ReportingState extends Equatable {
|
||||
const ReportingState();
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ReportingInitial extends ReportingState {
|
||||
const ReportingInitial();
|
||||
}
|
||||
|
||||
class ReportingLoading extends ReportingState {
|
||||
const ReportingLoading();
|
||||
}
|
||||
|
||||
class ReportingLoaded extends ReportingState {
|
||||
final List<RapportTrimestriel> rapports;
|
||||
final int annee;
|
||||
const ReportingLoaded({required this.rapports, required this.annee});
|
||||
@override
|
||||
List<Object?> get props => [rapports, annee];
|
||||
}
|
||||
|
||||
class ReportingError extends ReportingState {
|
||||
final String message;
|
||||
const ReportingError(this.message);
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
// ── Bloc ─────────────────────────────────────────────────────────────
|
||||
@injectable
|
||||
class ReportingBloc extends Bloc<ReportingEvent, ReportingState> {
|
||||
final ReportingRepository repository;
|
||||
|
||||
ReportingBloc(this.repository) : super(const ReportingInitial()) {
|
||||
on<LoadRapports>(_onLoad);
|
||||
on<GenererRapport>(_onGenerer);
|
||||
on<SignerRapport>(_onSigner);
|
||||
on<ArchiverRapport>(_onArchiver);
|
||||
}
|
||||
|
||||
Future<void> _onLoad(LoadRapports event, Emitter<ReportingState> emit) async {
|
||||
emit(const ReportingLoading());
|
||||
try {
|
||||
final annee = event.annee ?? DateTime.now().year;
|
||||
final rapports = await repository.lister(annee: annee);
|
||||
emit(ReportingLoaded(rapports: rapports, annee: annee));
|
||||
} catch (e, st) {
|
||||
AppLogger.error('ReportingBloc: load', error: e, stackTrace: st);
|
||||
emit(ReportingError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onGenerer(GenererRapport event, Emitter<ReportingState> emit) async {
|
||||
final current = state;
|
||||
try {
|
||||
await repository.generer(annee: event.annee, trimestre: event.trimestre);
|
||||
// Reload list
|
||||
add(LoadRapports(annee: current is ReportingLoaded ? current.annee : event.annee));
|
||||
} catch (e) {
|
||||
emit(ReportingError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onSigner(SignerRapport event, Emitter<ReportingState> emit) async {
|
||||
final current = state;
|
||||
try {
|
||||
await repository.signer(event.id, event.signataireId);
|
||||
add(LoadRapports(annee: current is ReportingLoaded ? current.annee : null));
|
||||
} catch (e) {
|
||||
emit(ReportingError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onArchiver(ArchiverRapport event, Emitter<ReportingState> emit) async {
|
||||
final current = state;
|
||||
try {
|
||||
await repository.archiver(event.id);
|
||||
add(LoadRapports(annee: current is ReportingLoaded ? current.annee : null));
|
||||
} catch (e) {
|
||||
emit(ReportingError(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user