feat(#112): reprise après refus via lien e-mail (/reprise?token=)

Branche le flux front : chargement du dossier, session reprise, wizards
parent/AM préremplis et resoumission via reprise-resoumettre.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 16:28:41 +02:00
co-authored by Cursor
parent c226c2fcdf
commit 671da71752
12 changed files with 575 additions and 3 deletions
@@ -33,6 +33,9 @@ class AmRegistrationData extends ChangeNotifier {
/// Places libres actuellement (0 ≤ valeur ≤ capacité) — API `places_disponibles`.
int? placesAvailable;
/// Photo déjà en base lors d'une reprise (#112) — sert pour l'affichage et `photo_url` API.
String? repriseExistingPhotoUrl;
// Step 3: Presentation & CGU
String presentationText = '';
bool cguAccepted = false;
@@ -104,6 +107,43 @@ class AmRegistrationData extends ChangeNotifier {
notifyListeners();
}
/// Reprise après refus (#112).
void resetForReprise({
required String firstName,
required String lastName,
required String phone,
required String email,
required String streetAddress,
required String postalCode,
required String city,
String? existingPhotoUrl,
}) {
this.firstName = firstName;
this.lastName = lastName;
this.phone = phone;
this.email = email;
this.streetAddress = streetAddress;
this.postalCode = postalCode;
this.city = city;
password = '';
repriseExistingPhotoUrl = existingPhotoUrl;
photoPath = existingPhotoUrl;
photoBytes = null;
photoFilename = null;
photoConsent = false;
dateOfBirth = null;
birthCity = '';
birthCountry = '';
nir = '';
agrementNumber = '';
agreementDate = null;
capacity = null;
placesAvailable = null;
presentationText = '';
cguAccepted = false;
notifyListeners();
}
// --- Getters for validation or display ---
bool get isStep1Complete =>
firstName.trim().length >= 2 &&
@@ -118,6 +158,8 @@ class AmRegistrationData extends ChangeNotifier {
/// Photo réelle (pas seulement un placeholder asset).
bool get _hasUserPhoto =>
(photoBytes != null && photoBytes!.isNotEmpty) ||
(repriseExistingPhotoUrl != null &&
repriseExistingPhotoUrl!.trim().isNotEmpty) ||
(photoPath != null &&
photoPath!.isNotEmpty &&
!photoPath!.startsWith('assets/'));
@@ -145,6 +187,9 @@ class AmRegistrationData extends ChangeNotifier {
bool get isRegistrationComplete =>
isStep1Complete && isStep2Complete && isStep3Complete;
/// Reprise (#112) : champs renvoyés par PATCH reprise-resoumettre.
bool get isRepriseSubmitReady => isStep1Complete && cguAccepted;
@override
String toString() {
return 'AmRegistrationData('
+53
View File
@@ -0,0 +1,53 @@
/// Réponse GET /auth/reprise-dossier. Ticket #111, #112.
class RepriseDossier {
final String id;
final String email;
final String? prenom;
final String? nom;
final String? telephone;
final String? adresse;
final String? ville;
final String? codePostal;
final String? numeroDossier;
final String role;
final String? photoUrl;
final String? genre;
final String? situationFamiliale;
const RepriseDossier({
required this.id,
required this.email,
this.prenom,
this.nom,
this.telephone,
this.adresse,
this.ville,
this.codePostal,
this.numeroDossier,
required this.role,
this.photoUrl,
this.genre,
this.situationFamiliale,
});
bool get isParent => role == 'parent';
bool get isAm => role == 'assistante_maternelle';
factory RepriseDossier.fromJson(Map<String, dynamic> json) {
return RepriseDossier(
id: json['id']?.toString() ?? '',
email: json['email']?.toString() ?? '',
prenom: json['prenom']?.toString(),
nom: json['nom']?.toString(),
telephone: json['telephone']?.toString(),
adresse: json['adresse']?.toString(),
ville: json['ville']?.toString(),
codePostal: json['code_postal']?.toString(),
numeroDossier: json['numero_dossier']?.toString(),
role: json['role']?.toString() ?? '',
photoUrl: json['photo_url']?.toString(),
genre: json['genre']?.toString(),
situationFamiliale: json['situation_familiale']?.toString(),
);
}
}
@@ -178,6 +178,43 @@ class UserRegistrationData extends ChangeNotifier {
notifyListeners();
}
/// Reprise après refus (#112) : réinitialise le flux parent avec les données dossier.
void resetForReprise({
required String firstName,
required String lastName,
required String phone,
required String email,
required String address,
required String postalCode,
required String city,
}) {
parent1 = ParentData(
firstName: firstName,
lastName: lastName,
phone: phone,
email: email,
address: address,
postalCode: postalCode,
city: city,
password: '',
);
parent2 = null;
children.clear();
motivationText = '';
cguAccepted = false;
bankDetails = null;
attestationCafNumber = '';
consentQuotientFamilial = false;
notifyListeners();
}
/// Reprise (#112) : coordonnées parent principal + CGU.
bool get isRepriseSubmitReady =>
parent1.firstName.isNotEmpty &&
parent1.lastName.isNotEmpty &&
parent1.email.isNotEmpty &&
cguAccepted;
// Méthode pour vérifier si toutes les données requises sont là (simplifié)
bool isRegistrationComplete() {
// Ajouter ici les validations nécessaires