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:
dahoud
2026-04-25 15:28:17 +00:00
parent 8c1a254e80
commit 6ba71fb014
8 changed files with 717 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:unionflow_mobile_apps/features/reporting/data/models/rapport_trimestriel_model.dart';
import 'package:unionflow_mobile_apps/features/reporting/domain/entities/rapport_trimestriel.dart';
void main() {
group('StatutRapportX', () {
test('fromCode parse correct', () {
expect(StatutRapportX.fromCode('DRAFT'), StatutRapport.draft);
expect(StatutRapportX.fromCode('SIGNE'), StatutRapport.signe);
expect(StatutRapportX.fromCode('ARCHIVE'), StatutRapport.archive);
expect(StatutRapportX.fromCode(null), StatutRapport.inconnu);
expect(StatutRapportX.fromCode('AUTRE'), StatutRapport.inconnu);
});
test('libellé / sévérité / code', () {
expect(StatutRapport.draft.code, 'DRAFT');
expect(StatutRapport.draft.libelle, 'Brouillon');
expect(StatutRapport.draft.severite, 'warning');
expect(StatutRapport.signe.code, 'SIGNE');
expect(StatutRapport.signe.severite, 'info');
expect(StatutRapport.archive.code, 'ARCHIVE');
expect(StatutRapport.archive.severite, 'success');
expect(StatutRapport.inconnu.code, '');
expect(StatutRapport.inconnu.severite, 'secondary');
});
});
group('RapportTrimestriel', () {
RapportTrimestriel build({int score = 75, StatutRapport statut = StatutRapport.draft}) {
return RapportTrimestriel(
id: 'r1', organisationId: 'o1', annee: 2026, trimestre: 1,
dateGeneration: DateTime(2026, 4, 1),
statut: statut, scoreConformite: score,
);
}
test('libellePeriode formaté', () {
expect(build().libellePeriode, 'T1 / 2026');
});
test('estDraft / estSigne / estArchive', () {
expect(build(statut: StatutRapport.draft).estDraft, isTrue);
expect(build(statut: StatutRapport.signe).estSigne, isTrue);
expect(build(statut: StatutRapport.archive).estArchive, isTrue);
});
test('scoreSeverite — score >=80 → success', () {
expect(build(score: 85).scoreSeverite, 'success');
});
test('scoreSeverite — 60..79 → warning', () {
expect(build(score: 70).scoreSeverite, 'warning');
});
test('scoreSeverite — <60 → danger', () {
expect(build(score: 50).scoreSeverite, 'danger');
});
});
group('RapportTrimestrielModel.fromJson', () {
test('parsing complet', () {
final json = {
'id': 'r-uuid', 'organisationId': 'o-uuid',
'annee': 2026, 'trimestre': 2,
'dateGeneration': '2026-04-01T10:00:00',
'statut': 'SIGNE', 'scoreConformite': 82,
'signataireId': 's-uuid',
'dateSignature': '2026-04-05T14:30:00',
'hashSha256': 'a' * 64,
};
final m = RapportTrimestrielModel.fromJson(json);
expect(m.id, 'r-uuid');
expect(m.annee, 2026);
expect(m.trimestre, 2);
expect(m.statut, StatutRapport.signe);
expect(m.scoreConformite, 82);
expect(m.signataireId, 's-uuid');
expect(m.hashSha256, 'a' * 64);
});
test('valeurs manquantes → fallbacks', () {
final m = RapportTrimestrielModel.fromJson(<String, dynamic>{});
expect(m.id, '');
expect(m.statut, StatutRapport.inconnu);
expect(m.scoreConformite, 0);
expect(m.dateGeneration, isNull);
});
test('date invalide → null', () {
final m = RapportTrimestrielModel.fromJson({'dateGeneration': 'not-a-date'});
expect(m.dateGeneration, isNull);
});
});
}