feat(#159/#160): suppressions métier dashboard — API + UI (squash develop).
Matrice PO #154 : supprimer dossiers famille/AM, parents, enfants et comptes staff avec règles métier (placements clôturés, sans_enfant, cascade foyer, droits gestionnaire/admin). Poubelle en liste, dialogues de confirmation, garde-fou gestionnaire→gestionnaire. Inclut polish hauteur modale fiche AM.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:p_tits_pas/models/dossier_list_item.dart';
|
||||
import 'package:p_tits_pas/services/auth_service.dart';
|
||||
import 'package:p_tits_pas/services/user_service.dart';
|
||||
import 'package:p_tits_pas/utils/staff_deletion_rights.dart';
|
||||
import 'package:p_tits_pas/widgets/admin/dossier_list_card.dart';
|
||||
import 'package:p_tits_pas/widgets/admin/pending_validation_widget.dart';
|
||||
import 'package:p_tits_pas/widgets/admin/common/suppression_confirm_dialog.dart';
|
||||
import 'package:p_tits_pas/widgets/admin/validation_dossier_modal.dart';
|
||||
|
||||
/// Onglet permanent « Dossiers » (#153) : pending en haut + liste unifiée en bas.
|
||||
@@ -27,13 +30,21 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
||||
List<DossierListItem> _all = [];
|
||||
Set<String> _pendingNumeros = {};
|
||||
int _pendingRefreshTick = 0;
|
||||
bool _canDelete = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadRights();
|
||||
_loadAll();
|
||||
}
|
||||
|
||||
Future<void> _loadRights() async {
|
||||
final user = await AuthService.getCurrentUser();
|
||||
if (!mounted) return;
|
||||
setState(() => _canDelete = canDeleteMetier(user?.role));
|
||||
}
|
||||
|
||||
Future<void> _loadAll() async {
|
||||
setState(() {
|
||||
_loading = true;
|
||||
@@ -42,12 +53,29 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
||||
try {
|
||||
final parents = await UserService.getParents();
|
||||
final ams = await UserService.getAssistantesMaternelles();
|
||||
Map<String, bool> sansEnfant = {};
|
||||
try {
|
||||
sansEnfant = await UserService.getSansEnfantByNumero();
|
||||
} catch (_) {
|
||||
// Flag optionnel : ne bloque pas la liste.
|
||||
}
|
||||
if (!mounted) return;
|
||||
final items = <DossierListItem>[
|
||||
...DossierListItem.fromParents(parents),
|
||||
...DossierListItem.fromAssistantes(ams),
|
||||
];
|
||||
].map((item) {
|
||||
if (!item.isFamille) return item;
|
||||
final apiFlag = sansEnfant[item.numeroDossier] == true;
|
||||
final localEmpty = (item.enfantsCount ?? 0) == 0;
|
||||
return item.copyWith(
|
||||
sansEnfant: apiFlag || localEmpty || item.sansEnfant,
|
||||
);
|
||||
}).toList();
|
||||
items.sort((a, b) {
|
||||
// Dossiers sans enfant en tête (#160), comme orphelins #157.
|
||||
final ae = a.sansEnfant ? 0 : 1;
|
||||
final be = b.sansEnfant ? 0 : 1;
|
||||
if (ae != be) return ae.compareTo(be);
|
||||
final byNum = a.numeroDossier.compareTo(b.numeroDossier);
|
||||
if (byNum != 0) return byNum;
|
||||
return a.typeLabel.compareTo(b.typeLabel);
|
||||
@@ -95,6 +123,95 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmDeleteDossier(DossierListItem item) async {
|
||||
final num = item.numeroDossier.trim();
|
||||
if (num.isEmpty) return;
|
||||
|
||||
var people = <SuppressionPersonLine>[];
|
||||
String? fallbackSummary;
|
||||
try {
|
||||
final dossier = await UserService.getDossier(num);
|
||||
if (dossier.isFamily) {
|
||||
final f = dossier.asFamily;
|
||||
people = suppressionPeopleFromDossier(
|
||||
isFamille: true,
|
||||
parents: f.parents
|
||||
.map((p) => (
|
||||
nom: p.nom ?? '',
|
||||
prenom: p.prenom ?? '',
|
||||
email: p.email,
|
||||
))
|
||||
.toList(),
|
||||
enfants: f.enfants
|
||||
.map((e) => (
|
||||
nom: e.lastName ?? '',
|
||||
prenom: e.firstName ?? '',
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
} else {
|
||||
final am = dossier.asAm.user;
|
||||
people = suppressionPeopleFromDossier(
|
||||
isFamille: false,
|
||||
parents: const [],
|
||||
enfants: const [],
|
||||
amName: formatDossierPersonLabel(
|
||||
nom: am.nom,
|
||||
prenom: am.prenom,
|
||||
email: am.email,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (_) {
|
||||
fallbackSummary = item.isFamille
|
||||
? 'Tous les parents et enfants rattachés seront supprimés.'
|
||||
: 'Le compte AM sera supprimé ; les enfants accueillis '
|
||||
'seront conservés.';
|
||||
if (item.namesLine.trim().isNotEmpty) {
|
||||
for (final part in item.namesLine.split(' - ')) {
|
||||
final label = part.trim();
|
||||
if (label.isEmpty) continue;
|
||||
people.add(SuppressionPersonLine(
|
||||
label: label,
|
||||
icon: item.isFamille
|
||||
? Icons.supervisor_account_outlined
|
||||
: Icons.face,
|
||||
role: item.isFamille ? 'Parent' : 'AM',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
final confirmed = await showDossierSuppressionConfirmDialog(
|
||||
context,
|
||||
numeroDossier: num,
|
||||
isFamille: item.isFamille,
|
||||
people: people,
|
||||
fallbackSummary: fallbackSummary,
|
||||
);
|
||||
if (!confirmed || !mounted) return;
|
||||
|
||||
try {
|
||||
final result = await UserService.deleteDossier(num);
|
||||
if (!mounted) return;
|
||||
final msg = (result['message'] ?? 'Dossier supprimé.').toString();
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
||||
await _refreshEverything();
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
e is Exception
|
||||
? e.toString().replaceFirst('Exception: ', '')
|
||||
: 'Erreur suppression',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final query = widget.searchQuery;
|
||||
@@ -115,6 +232,7 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
||||
key: ValueKey('pending-$_pendingRefreshTick'),
|
||||
searchQuery: query,
|
||||
compactWhenEmpty: true,
|
||||
canDelete: _canDelete,
|
||||
onPendingNumerosChanged: (nums) {
|
||||
if (!mounted) return;
|
||||
setState(() => _pendingNumeros = nums);
|
||||
@@ -189,7 +307,15 @@ class _DossiersManagementWidgetState extends State<DossiersManagementWidget> {
|
||||
namesLine: item.namesLine,
|
||||
isFamille: item.isFamille,
|
||||
photoUrl: item.photoUrl,
|
||||
sansEnfant: item.sansEnfant,
|
||||
enfantsCount: item.enfantsCount,
|
||||
vigilanceTooltip: item.sansEnfant
|
||||
? 'Aucun enfant rattaché à ce dossier famille'
|
||||
: null,
|
||||
onOpen: () => _openDossier(item.numeroDossier),
|
||||
onDelete: _canDelete
|
||||
? () => _confirmDeleteDossier(item)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
childCount: filtered.length,
|
||||
|
||||
Reference in New Issue
Block a user