- DTO inscription enfant : champ consent_photo - Inscription + reprise parent : sauvegarde bool + date - Front payload : envoi consent_photo - PATCH /enfants : horodatage du consentement Co-authored-by: Cursor <cursoragent@cursor.com>
224 lines
7.1 KiB
Dart
224 lines
7.1 KiB
Dart
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';
|
|
import 'package:p_tits_pas/models/user_registration_data.dart';
|
|
import 'package:p_tits_pas/services/api/api_config.dart';
|
|
|
|
/// Mapping GET reprise-dossier → modèles wizard. Ticket #112.
|
|
class RepriseMapper {
|
|
RepriseMapper._();
|
|
|
|
static const List<CardColorVertical> _childCardColors = [
|
|
CardColorVertical.lavender,
|
|
CardColorVertical.pink,
|
|
CardColorVertical.peach,
|
|
CardColorVertical.lime,
|
|
CardColorVertical.red,
|
|
CardColorVertical.green,
|
|
CardColorVertical.blue,
|
|
];
|
|
|
|
static ParentData parentFromDossier(ParentDossier p) {
|
|
return ParentData(
|
|
firstName: p.prenom ?? '',
|
|
lastName: p.nom ?? '',
|
|
phone: p.telephone ?? '',
|
|
email: p.email,
|
|
address: p.adresse ?? '',
|
|
postalCode: p.codePostal ?? '',
|
|
city: p.ville ?? '',
|
|
password: '',
|
|
);
|
|
}
|
|
|
|
static ChildData childFromEnfant(EnfantDossier e, int index) {
|
|
final isUnborn = e.status == 'a_naitre';
|
|
final dob = isUnborn
|
|
? isoToDdMmYyyy(e.dueDate)
|
|
: isoToDdMmYyyy(e.birthDate);
|
|
final photo = e.photoUrl?.trim();
|
|
final hasPhoto = photo != null && photo.isNotEmpty;
|
|
return ChildData(
|
|
firstName: e.firstName ?? '',
|
|
lastName: e.lastName ?? '',
|
|
dob: dob,
|
|
genre: e.gender ?? '',
|
|
// Legacy : dossiers inscrits avant #144 avaient consent_photo=false malgré une photo.
|
|
// On pré-coche encore si photo en base pour ne pas bloquer la reprise.
|
|
photoConsent: e.consentPhoto || hasPhoto,
|
|
multipleBirth: e.estMultiple,
|
|
isUnbornChild: isUnborn,
|
|
cardColor: _childCardColors[index % _childCardColors.length],
|
|
repriseChildId: e.id,
|
|
existingPhotoUrl: photo != null && photo.isNotEmpty
|
|
? ApiConfig.absoluteMediaUrl(photo)
|
|
: null,
|
|
);
|
|
}
|
|
|
|
static void applyParentDossier(UserRegistrationData data, RepriseDossier dossier) {
|
|
ParentDossier? titulaire;
|
|
ParentDossier? coParent;
|
|
|
|
if (dossier.parents.isNotEmpty) {
|
|
for (final p in dossier.parents) {
|
|
if (p.id == dossier.id) {
|
|
titulaire = p;
|
|
} else {
|
|
coParent = p;
|
|
}
|
|
}
|
|
titulaire ??= dossier.parents.first;
|
|
if (coParent == null && dossier.parents.length > 1) {
|
|
coParent = dossier.parents.firstWhere(
|
|
(p) => p.id != titulaire!.id,
|
|
orElse: () => dossier.parents.last,
|
|
);
|
|
}
|
|
}
|
|
|
|
final p1 = titulaire != null
|
|
? parentFromDossier(titulaire)
|
|
: ParentData(
|
|
firstName: dossier.prenom ?? '',
|
|
lastName: dossier.nom ?? '',
|
|
phone: dossier.telephone ?? '',
|
|
email: dossier.email,
|
|
address: dossier.adresse ?? '',
|
|
postalCode: dossier.codePostal ?? '',
|
|
city: dossier.ville ?? '',
|
|
password: '',
|
|
);
|
|
|
|
final children = dossier.enfants
|
|
.asMap()
|
|
.entries
|
|
.map((e) => childFromEnfant(e.value, e.key))
|
|
.toList();
|
|
|
|
data.resetForReprise(
|
|
parent1Data: p1,
|
|
parent2Data: coParent != null ? parentFromDossier(coParent) : null,
|
|
childrenData: children,
|
|
motivation: dossier.texteMotivation ?? '',
|
|
);
|
|
}
|
|
|
|
static DateTime? parseIsoDate(String? raw) {
|
|
if (raw == null || raw.trim().isEmpty) return null;
|
|
final s = raw.trim();
|
|
final iso = RegExp(r'^(\d{4})-(\d{2})-(\d{2})');
|
|
final isoMatch = iso.firstMatch(s);
|
|
if (isoMatch != null) {
|
|
return DateTime(
|
|
int.parse(isoMatch.group(1)!),
|
|
int.parse(isoMatch.group(2)!),
|
|
int.parse(isoMatch.group(3)!),
|
|
);
|
|
}
|
|
try {
|
|
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 || s == 'null' ? null : s;
|
|
}
|
|
if (value is DateTime) {
|
|
return '${value.year.toString().padLeft(4, '0')}-'
|
|
'${value.month.toString().padLeft(2, '0')}-'
|
|
'${value.day.toString().padLeft(2, '0')}';
|
|
}
|
|
if (value is num) {
|
|
final ms = value.abs() > 9999999999
|
|
? value.toInt()
|
|
: value.toInt() * 1000;
|
|
final dt = DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true);
|
|
return '${dt.year.toString().padLeft(4, '0')}-'
|
|
'${dt.month.toString().padLeft(2, '0')}-'
|
|
'${dt.day.toString().padLeft(2, '0')}';
|
|
}
|
|
if (value is Map) {
|
|
final y = value['year'];
|
|
final m = value['month'] ?? value['monthValue'];
|
|
final d = value['day'] ?? value['dayOfMonth'];
|
|
if (y is num && m is num && d is num) {
|
|
return '${y.toInt().toString().padLeft(4, '0')}-'
|
|
'${m.toInt().toString().padLeft(2, '0')}-'
|
|
'${d.toInt().toString().padLeft(2, '0')}';
|
|
}
|
|
}
|
|
final s = value.toString().trim();
|
|
return s.isEmpty || s == 'null' ? null : s;
|
|
}
|
|
|
|
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 '';
|
|
return '${dt.day.toString().padLeft(2, '0')}/'
|
|
'${dt.month.toString().padLeft(2, '0')}/'
|
|
'${dt.year}';
|
|
}
|
|
}
|