feat(#112): aligner reprise front sur dossier complet GET/PATCH

Préremplit parents, enfants, motivation et fiche AM depuis reprise-dossier
et envoie le body PATCH complet (co-parent, enfants par id, champs pro AM).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-16 16:46:51 +02:00
co-authored by Cursor
parent 25c10c885a
commit f300505225
11 changed files with 439 additions and 92 deletions
+22 -12
View File
@@ -117,6 +117,16 @@ class AmRegistrationData extends ChangeNotifier {
required String postalCode,
required String city,
String? existingPhotoUrl,
bool consentementPhoto = false,
DateTime? dateOfBirth,
String birthCity = '',
String birthCountry = '',
String nir = '',
String agrementNumber = '',
DateTime? agreementDate,
int? capacity,
int? placesAvailable,
String presentationText = '',
}) {
this.firstName = firstName;
this.lastName = lastName;
@@ -130,16 +140,16 @@ class AmRegistrationData extends ChangeNotifier {
photoPath = existingPhotoUrl;
photoBytes = null;
photoFilename = null;
photoConsent = false;
dateOfBirth = null;
birthCity = '';
birthCountry = '';
nir = '';
agrementNumber = '';
agreementDate = null;
capacity = null;
placesAvailable = null;
presentationText = '';
photoConsent = consentementPhoto;
dateOfBirth = dateOfBirth;
this.birthCity = birthCity;
this.birthCountry = birthCountry;
this.nir = nir;
this.agrementNumber = agrementNumber;
agreementDate = agreementDate;
this.capacity = capacity;
placesAvailable = placesAvailable;
this.presentationText = presentationText;
cguAccepted = false;
notifyListeners();
}
@@ -187,8 +197,8 @@ class AmRegistrationData extends ChangeNotifier {
bool get isRegistrationComplete =>
isStep1Complete && isStep2Complete && isStep3Complete;
/// Reprise (#112) : champs renvoyés par PATCH reprise-resoumettre.
bool get isRepriseSubmitReady => isStep1Complete && cguAccepted;
/// Reprise (#112) : dossier AM complet renvoyé par PATCH reprise-resoumettre.
bool get isRepriseSubmitReady => isRegistrationComplete;
@override
String toString() {
+4
View File
@@ -184,6 +184,7 @@ class EnfantDossier {
final String? dueDate;
final String? photoUrl;
final bool consentPhoto;
final bool estMultiple;
EnfantDossier({
required this.id,
@@ -195,6 +196,7 @@ class EnfantDossier {
this.dueDate,
this.photoUrl,
this.consentPhoto = false,
this.estMultiple = false,
});
String get fullName => '${firstName ?? ''} ${lastName ?? ''}'.trim();
@@ -223,6 +225,8 @@ class EnfantDossier {
photoUrl: resolvedPhoto,
consentPhoto:
json['consent_photo'] == true || json['consentPhoto'] == true,
estMultiple:
json['est_multiple'] == true || json['estMultiple'] == true,
);
}
}
+65 -1
View File
@@ -1,4 +1,6 @@
/// Réponse GET /auth/reprise-dossier. Ticket #111, #112.
import 'package:p_tits_pas/models/dossier_unifie.dart';
/// Réponse GET /auth/reprise-dossier. Tickets #111, #112.
class RepriseDossier {
final String id;
final String email;
@@ -14,6 +16,21 @@ class RepriseDossier {
final String? genre;
final String? situationFamiliale;
final List<ParentDossier> parents;
final List<EnfantDossier> enfants;
final String? texteMotivation;
final bool? consentementPhoto;
final String? dateNaissance;
final String? lieuNaissanceVille;
final String? lieuNaissancePays;
final String? numeroAgrement;
final String? nir;
final String? dateAgrement;
final int? nbMaxEnfants;
final int? placeDisponible;
final String? biographie;
const RepriseDossier({
required this.id,
required this.email,
@@ -28,12 +45,44 @@ class RepriseDossier {
this.photoUrl,
this.genre,
this.situationFamiliale,
this.parents = const [],
this.enfants = const [],
this.texteMotivation,
this.consentementPhoto,
this.dateNaissance,
this.lieuNaissanceVille,
this.lieuNaissancePays,
this.numeroAgrement,
this.nir,
this.dateAgrement,
this.nbMaxEnfants,
this.placeDisponible,
this.biographie,
});
bool get isParent => role == 'parent';
bool get isAm => role == 'assistante_maternelle';
factory RepriseDossier.fromJson(Map<String, dynamic> json) {
final parentsRaw = json['parents'];
final parentsList = parentsRaw is List
? parentsRaw
.where((e) => e is Map)
.map((e) => ParentDossier.fromJson(Map<String, dynamic>.from(e as Map)))
.toList()
: <ParentDossier>[];
final enfantsRaw = json['enfants'];
final enfantsList = enfantsRaw is List
? enfantsRaw
.where((e) => e is Map)
.map((e) => EnfantDossier.fromJson(Map<String, dynamic>.from(e as Map)))
.toList()
: <EnfantDossier>[];
final nbMax = json['nb_max_enfants'];
final places = json['place_disponible'];
return RepriseDossier(
id: json['id']?.toString() ?? '',
email: json['email']?.toString() ?? '',
@@ -48,6 +97,21 @@ class RepriseDossier {
photoUrl: json['photo_url']?.toString(),
genre: json['genre']?.toString(),
situationFamiliale: json['situation_familiale']?.toString(),
parents: parentsList,
enfants: enfantsList,
texteMotivation: (json['texte_motivation'] ?? json['presentation_dossier'])
?.toString(),
consentementPhoto: json['consentement_photo'] == true,
dateNaissance: json['date_naissance']?.toString(),
lieuNaissanceVille: json['lieu_naissance_ville']?.toString(),
lieuNaissancePays: json['lieu_naissance_pays']?.toString(),
numeroAgrement: json['numero_agrement']?.toString(),
nir: json['nir']?.toString(),
dateAgrement: json['date_agrement']?.toString(),
nbMaxEnfants: nbMax is int ? nbMax : (nbMax is num ? nbMax.toInt() : null),
placeDisponible:
places is int ? places : (places is num ? places.toInt() : null),
biographie: json['biographie']?.toString(),
);
}
}
+26 -21
View File
@@ -43,6 +43,10 @@ class ChildData {
/// Octets de la photo (fiable à lenvoi API ; [imageFile] peut être absent sur le web).
Uint8List? imageBytes;
CardColorVertical cardColor; // Nouveau champ pour la couleur de la carte
/// UUID enfant en base (reprise #112) — requis pour PATCH reprise-resoumettre.
String? repriseChildId;
/// Photo déjà stockée (affichage reprise sans re-upload).
String? existingPhotoUrl;
ChildData({
this.firstName = '',
@@ -55,6 +59,8 @@ class ChildData {
this.imageFile,
this.imageBytes,
required this.cardColor, // Rendre requis dans le constructeur
this.repriseChildId,
this.existingPhotoUrl,
});
ChildData copyWith({
@@ -68,6 +74,8 @@ class ChildData {
Object? imageFile = _unsetImage,
Object? imageBytes = _unsetImageBytes,
CardColorVertical? cardColor,
String? repriseChildId,
String? existingPhotoUrl,
}) {
return ChildData(
firstName: firstName ?? this.firstName,
@@ -81,6 +89,8 @@ class ChildData {
imageBytes:
identical(imageBytes, _unsetImageBytes) ? this.imageBytes : imageBytes as Uint8List?,
cardColor: cardColor ?? this.cardColor,
repriseChildId: repriseChildId ?? this.repriseChildId,
existingPhotoUrl: existingPhotoUrl ?? this.existingPhotoUrl,
);
}
}
@@ -180,27 +190,17 @@ class UserRegistrationData extends ChangeNotifier {
/// 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,
required ParentData parent1Data,
ParentData? parent2Data,
List<ChildData>? childrenData,
String motivation = '',
}) {
parent1 = ParentData(
firstName: firstName,
lastName: lastName,
phone: phone,
email: email,
address: address,
postalCode: postalCode,
city: city,
password: '',
);
parent2 = null;
children.clear();
motivationText = '';
parent1 = parent1Data;
parent2 = parent2Data;
children
..clear()
..addAll(childrenData ?? const []);
motivationText = motivation;
cguAccepted = false;
bankDetails = null;
attestationCafNumber = '';
@@ -208,11 +208,16 @@ class UserRegistrationData extends ChangeNotifier {
notifyListeners();
}
/// Reprise (#112) : coordonnées parent principal + CGU.
/// Reprise (#112) : coordonnées + enfants connus + CGU.
bool get isRepriseSubmitReady =>
parent1.firstName.isNotEmpty &&
parent1.lastName.isNotEmpty &&
parent1.email.isNotEmpty &&
children.isNotEmpty &&
children.every(
(c) =>
c.repriseChildId != null && c.repriseChildId!.trim().isNotEmpty,
) &&
cguAccepted;
// Méthode pour vérifier si toutes les données requises sont là (simplifié)