Files
unionflow-mobile-apps/lib/features/reporting/presentation/bloc/reporting_bloc.dart
dahoud 6ba71fb014 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.
2026-04-25 15:28:17 +00:00

130 lines
4.2 KiB
Dart

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()));
}
}
}