fix(#112): préremplissage AM complet en reprise + lien login repositionné

Parse dates/places/consentement de façon robuste, rafraîchit l'étape 2 AM
et place « J'ai un numéro de dossier » sous « Créer un compte ».

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-06-16 17:29:17 +02:00
parent 9d54d9b19b
commit c26ed00374
5 changed files with 93 additions and 38 deletions

View File

@ -1,4 +1,5 @@
import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/utils/reprise_mapper.dart';
/// Réponse GET /auth/reprise-dossier. Tickets #111, #112.
class RepriseDossier {
@ -20,7 +21,7 @@ class RepriseDossier {
final List<EnfantDossier> enfants;
final String? texteMotivation;
final bool? consentementPhoto;
final bool consentementPhoto;
final String? dateNaissance;
final String? lieuNaissanceVille;
final String? lieuNaissancePays;
@ -48,7 +49,7 @@ class RepriseDossier {
this.parents = const [],
this.enfants = const [],
this.texteMotivation,
this.consentementPhoto,
this.consentementPhoto = false,
this.dateNaissance,
this.lieuNaissanceVille,
this.lieuNaissancePays,
@ -80,9 +81,6 @@ class RepriseDossier {
.toList()
: <EnfantDossier>[];
final nbMax = json['nb_max_enfants'];
final places = json['place_disponible'];
return RepriseDossier(
id: json['id']?.toString() ?? '',
email: json['email']?.toString() ?? '',
@ -101,17 +99,22 @@ class RepriseDossier {
enfants: enfantsList,
texteMotivation: (json['texte_motivation'] ?? json['presentation_dossier'])
?.toString(),
consentementPhoto: json['consentement_photo'] == true,
dateNaissance: json['date_naissance']?.toString(),
consentementPhoto: RepriseMapper.optionalBool(json['consentement_photo']) ||
RepriseMapper.optionalBool(json['consent_photo']),
dateNaissance: RepriseMapper.optionalDateString(json['date_naissance']),
lieuNaissanceVille: json['lieu_naissance_ville']?.toString(),
lieuNaissancePays: json['lieu_naissance_pays']?.toString(),
numeroAgrement: json['numero_agrement']?.toString(),
numeroAgrement: (json['numero_agrement'] ?? json['numero_agrement_am'])
?.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(),
dateAgrement: RepriseMapper.optionalDateString(json['date_agrement']),
nbMaxEnfants: RepriseMapper.optionalInt(
json['nb_max_enfants'] ?? json['capacite_accueil'],
),
placeDisponible: RepriseMapper.optionalInt(
json['place_disponible'] ?? json['places_disponibles'],
),
biographie: (json['biographie'] ?? json['presentation'])?.toString(),
);
}
}

View File

@ -29,6 +29,12 @@ class AmRegisterStep2Screen extends StatelessWidget {
);
return ProfessionalInfoFormScreen(
key: ValueKey(
'am-pro-${registrationData.dateOfBirth?.millisecondsSinceEpoch}'
'-${registrationData.agreementDate?.millisecondsSinceEpoch}'
'-${registrationData.placesAvailable}'
'-${registrationData.nir}',
),
stepText: 'Étape 2/4',
title: 'Vos informations professionnelles',
cardColor: CardColorHorizontal.green,

View File

@ -377,8 +377,6 @@ class _LoginPageState extends State<LoginScreen> {
),
),
const SizedBox(height: 10),
Center(child: _buildRepriseDossierLink()),
const SizedBox(height: 10),
// Lien mot de passe oublié
Center(
child: TextButton(
@ -412,6 +410,7 @@ class _LoginPageState extends State<LoginScreen> {
),
),
),
Center(child: _buildRepriseDossierLink()),
],
),
),
@ -645,7 +644,6 @@ class _LoginPageState extends State<LoginScreen> {
onPressed: _handleLogin,
),
const SizedBox(height: 12),
_buildRepriseDossierLink(),
TextButton(
onPressed: () => context.go('/forgot-password'),
child: Text(
@ -669,6 +667,7 @@ class _LoginPageState extends State<LoginScreen> {
),
),
),
_buildRepriseDossierLink(),
],
),
),

View File

@ -53,26 +53,6 @@ class RepriseSession {
}
static void applyToAm(AmRegistrationData data, RepriseDossier dossier) {
final displayPhoto = _photoUrl;
data.resetForReprise(
firstName: dossier.prenom ?? '',
lastName: dossier.nom ?? '',
phone: dossier.telephone ?? '',
email: dossier.email,
streetAddress: dossier.adresse ?? '',
postalCode: dossier.codePostal ?? '',
city: dossier.ville ?? '',
existingPhotoUrl: displayPhoto,
consentementPhoto: dossier.consentementPhoto ?? false,
dateOfBirth: RepriseMapper.parseIsoDate(dossier.dateNaissance),
birthCity: dossier.lieuNaissanceVille ?? '',
birthCountry: dossier.lieuNaissancePays ?? '',
nir: dossier.nir ?? '',
agrementNumber: dossier.numeroAgrement ?? '',
agreementDate: RepriseMapper.parseIsoDate(dossier.dateAgrement),
capacity: dossier.nbMaxEnfants,
placesAvailable: dossier.placeDisponible,
presentationText: dossier.biographie ?? '',
);
RepriseMapper.applyAmDossier(data, dossier);
}
}

View File

@ -1,3 +1,4 @@
import 'package:p_tits_pas/models/am_registration_data.dart';
import 'package:p_tits_pas/models/card_assets.dart';
import 'package:p_tits_pas/models/dossier_unifie.dart';
import 'package:p_tits_pas/models/reprise_dossier.dart';
@ -106,13 +107,79 @@ class RepriseMapper {
static DateTime? parseIsoDate(String? raw) {
if (raw == null || raw.trim().isEmpty) return null;
final s = raw.trim();
try {
return DateTime.parse(raw.trim());
return DateTime.parse(s);
} catch (_) {
final parts = s.split('/');
if (parts.length == 3) {
final day = int.tryParse(parts[0]);
final month = int.tryParse(parts[1]);
final year = int.tryParse(parts[2]);
if (day != null && month != null && year != null) {
return DateTime(year, month, day);
}
}
return null;
}
}
static String? optionalDateString(dynamic value) {
if (value == null) return null;
if (value is String) {
final s = value.trim();
return s.isEmpty ? null : s;
}
if (value is DateTime) return value.toIso8601String();
return null;
}
static int? optionalInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is num) return value.toInt();
if (value is String) {
final s = value.trim();
if (s.isEmpty) return null;
return int.tryParse(s);
}
return null;
}
static bool optionalBool(dynamic value) {
if (value == true) return true;
if (value is String && value.toLowerCase() == 'true') return true;
return false;
}
static void applyAmDossier(AmRegistrationData data, RepriseDossier dossier) {
final rawPhoto = dossier.photoUrl?.trim();
final hasPhoto = rawPhoto != null && rawPhoto.isNotEmpty;
final displayPhoto =
hasPhoto ? ApiConfig.absoluteMediaUrl(rawPhoto) : null;
data.resetForReprise(
firstName: dossier.prenom ?? '',
lastName: dossier.nom ?? '',
phone: dossier.telephone ?? '',
email: dossier.email,
streetAddress: dossier.adresse ?? '',
postalCode: dossier.codePostal ?? '',
city: dossier.ville ?? '',
existingPhotoUrl: displayPhoto,
consentementPhoto: dossier.consentementPhoto || hasPhoto,
dateOfBirth: parseIsoDate(dossier.dateNaissance),
birthCity: dossier.lieuNaissanceVille ?? '',
birthCountry: dossier.lieuNaissancePays ?? '',
nir: dossier.nir ?? '',
agrementNumber: dossier.numeroAgrement ?? '',
agreementDate: parseIsoDate(dossier.dateAgrement),
capacity: dossier.nbMaxEnfants,
placesAvailable: dossier.placeDisponible,
presentationText: dossier.biographie ?? '',
);
}
static String isoToDdMmYyyy(String? iso) {
final dt = parseIsoDate(iso);
if (dt == null) return '';