petitspas/frontend/lib/utils/reprise_mapper.dart
Julien Martin 77d952a6f7 fix(#144): persister le consentement photo des enfants
- 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>
2026-07-17 12:36:05 +02:00

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}';
}
}