feat(#131): fiches parent/AM éditable, placement AM↔enfant, statuts garde/sans_garde
Squash merge develop → master. - Fiche parent éditable (co-parent, PATCH fiche, GET /parents) - Fiche AM 3 onglets (PATCH fiche, rattacher/détacher enfants) - Table enfants_assistantes_maternelles + enum garde/sans_garde - Migration SQL + BDD.sql canonique - Correctifs recette : @Get() parents, DTO fiche AM, fix NIR Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -61,6 +61,7 @@ class ApiConfig {
|
||||
static const String gestionnaires = '/gestionnaires';
|
||||
static const String parents = '/parents';
|
||||
static const String assistantesMaternelles = '/assistantes-maternelles';
|
||||
static const String enfants = '/enfants';
|
||||
static const String relais = '/relais';
|
||||
static const String dossiers = '/dossiers';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:p_tits_pas/models/user.dart';
|
||||
import 'package:p_tits_pas/models/enfant_admin_model.dart';
|
||||
import 'package:p_tits_pas/models/parent_model.dart';
|
||||
import 'package:p_tits_pas/models/assistante_maternelle_model.dart';
|
||||
import 'package:p_tits_pas/models/pending_family.dart';
|
||||
@@ -366,7 +367,136 @@ class UserService {
|
||||
}
|
||||
|
||||
final List<dynamic> data = jsonDecode(response.body);
|
||||
return data.map((e) => ParentModel.fromJson(e)).toList();
|
||||
return data
|
||||
.map((e) => ParentModel.fromJson(Map<String, dynamic>.from(e as Map)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<ParentModel> getParent(String userId) async {
|
||||
final response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$userId'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
final err = jsonDecode(response.body) as Map<String, dynamic>?;
|
||||
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement parent');
|
||||
}
|
||||
final decoded = jsonDecode(response.body);
|
||||
return ParentModel.fromJson(
|
||||
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<ParentModel> updateParentFiche({
|
||||
required String parentUserId,
|
||||
required Map<String, dynamic> body,
|
||||
}) async {
|
||||
final response = await http.patch(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/fiche'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour parent'));
|
||||
}
|
||||
return _parentModelFromBody(response.body);
|
||||
}
|
||||
|
||||
static Future<ParentModel> attachEnfantToParent({
|
||||
required String parentUserId,
|
||||
required String enfantId,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
|
||||
),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
|
||||
}
|
||||
return _parentModelFromBody(response.body);
|
||||
}
|
||||
|
||||
static Future<ParentModel> detachEnfantFromParent({
|
||||
required String parentUserId,
|
||||
required String enfantId,
|
||||
}) async {
|
||||
final response = await http.delete(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.parents}/$parentUserId/enfants/$enfantId',
|
||||
),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
|
||||
}
|
||||
if (response.body.isEmpty) {
|
||||
return getParent(parentUserId);
|
||||
}
|
||||
return _parentModelFromBody(response.body);
|
||||
}
|
||||
|
||||
static Future<List<EnfantAdminModel>> getEnfants() async {
|
||||
final response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfants'));
|
||||
}
|
||||
final List<dynamic> data = jsonDecode(response.body);
|
||||
return data
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(EnfantAdminModel.fromJson)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<EnfantAdminModel> getEnfant(String enfantId) async {
|
||||
final response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur chargement enfant'));
|
||||
}
|
||||
return EnfantAdminModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
static Future<EnfantAdminModel> updateEnfant({
|
||||
required String enfantId,
|
||||
required Map<String, dynamic> body,
|
||||
}) async {
|
||||
final response = await http.patch(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.enfants}/$enfantId'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur mise à jour enfant'));
|
||||
}
|
||||
return EnfantAdminModel.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
static ParentModel _parentModelFromBody(String body) {
|
||||
final decoded = jsonDecode(body);
|
||||
return ParentModel.fromJson(
|
||||
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
|
||||
);
|
||||
}
|
||||
|
||||
static String _extractErrorMessage(String body, String fallback) {
|
||||
try {
|
||||
final decoded = jsonDecode(body);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
final message = decoded['message'];
|
||||
if (message is List && message.isNotEmpty) {
|
||||
return message.join(' - ');
|
||||
}
|
||||
return _toStr(message) ?? fallback;
|
||||
}
|
||||
} catch (_) {}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// Récupérer la liste des assistantes maternelles
|
||||
@@ -383,7 +513,153 @@ class UserService {
|
||||
}
|
||||
|
||||
final List<dynamic> data = jsonDecode(response.body);
|
||||
return data.map((e) => AssistanteMaternelleModel.fromJson(e)).toList();
|
||||
return data
|
||||
.map((e) => AssistanteMaternelleModel.fromJson(
|
||||
Map<String, dynamic>.from(e as Map),
|
||||
))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<AssistanteMaternelleModel> getAssistanteMaternelle(
|
||||
String userId,
|
||||
) async {
|
||||
final response = await http.get(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$userId'),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
return AssistanteMaternelleModel.fromJson(
|
||||
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
|
||||
);
|
||||
}
|
||||
if (response.statusCode == 403 || response.statusCode == 404) {
|
||||
final all = await getAssistantesMaternelles();
|
||||
return all.firstWhere(
|
||||
(a) => a.user.id == userId,
|
||||
orElse: () => throw Exception('Assistante maternelle introuvable'),
|
||||
);
|
||||
}
|
||||
final err = jsonDecode(response.body) as Map<String, dynamic>?;
|
||||
throw Exception(_toStr(err?['message']) ?? 'Erreur chargement AM');
|
||||
}
|
||||
|
||||
/// Mise à jour fiche AM (identité + champs pro). Ticket #131.
|
||||
static Future<AssistanteMaternelleModel> updateAmFiche({
|
||||
required String amUserId,
|
||||
required Map<String, dynamic> body,
|
||||
}) async {
|
||||
final ficheResponse = await http.patch(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/fiche',
|
||||
),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
if (ficheResponse.statusCode == 200) {
|
||||
return _amModelFromBody(ficheResponse.body);
|
||||
}
|
||||
if (ficheResponse.statusCode != 404) {
|
||||
throw Exception(
|
||||
_extractErrorMessage(ficheResponse.body, 'Erreur mise à jour AM'),
|
||||
);
|
||||
}
|
||||
|
||||
final userFields = <String, dynamic>{};
|
||||
for (final k in [
|
||||
'nom',
|
||||
'prenom',
|
||||
'email',
|
||||
'telephone',
|
||||
'adresse',
|
||||
'ville',
|
||||
'code_postal',
|
||||
'statut',
|
||||
'date_naissance',
|
||||
'lieu_naissance_ville',
|
||||
'lieu_naissance_pays',
|
||||
]) {
|
||||
if (body.containsKey(k)) userFields[k] = body[k];
|
||||
}
|
||||
|
||||
final proFields = <String, dynamic>{};
|
||||
for (final entry in body.entries) {
|
||||
if (!userFields.containsKey(entry.key)) {
|
||||
proFields[entry.key] = entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (userFields.isNotEmpty) {
|
||||
final userResponse = await http.patch(
|
||||
Uri.parse('${ApiConfig.baseUrl}${ApiConfig.users}/$amUserId'),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(userFields),
|
||||
);
|
||||
if (userResponse.statusCode != 200) {
|
||||
throw Exception(
|
||||
_extractErrorMessage(userResponse.body, 'Erreur mise à jour identité'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (proFields.isNotEmpty) {
|
||||
final proResponse = await http.patch(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId',
|
||||
),
|
||||
headers: await _headers(),
|
||||
body: jsonEncode(proFields),
|
||||
);
|
||||
if (proResponse.statusCode != 200) {
|
||||
throw Exception(
|
||||
_extractErrorMessage(proResponse.body, 'Erreur mise à jour fiche pro'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return getAssistanteMaternelle(amUserId);
|
||||
}
|
||||
|
||||
static Future<AssistanteMaternelleModel> attachEnfantToAm({
|
||||
required String amUserId,
|
||||
required String enfantId,
|
||||
}) async {
|
||||
final response = await http.post(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
|
||||
),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 201) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur rattachement enfant'));
|
||||
}
|
||||
return _amModelFromBody(response.body);
|
||||
}
|
||||
|
||||
static Future<AssistanteMaternelleModel> detachEnfantFromAm({
|
||||
required String amUserId,
|
||||
required String enfantId,
|
||||
}) async {
|
||||
final response = await http.delete(
|
||||
Uri.parse(
|
||||
'${ApiConfig.baseUrl}${ApiConfig.assistantesMaternelles}/$amUserId/enfants/$enfantId',
|
||||
),
|
||||
headers: await _headers(),
|
||||
);
|
||||
if (response.statusCode != 200 && response.statusCode != 204) {
|
||||
throw Exception(_extractErrorMessage(response.body, 'Erreur détachement enfant'));
|
||||
}
|
||||
if (response.body.isEmpty) {
|
||||
return getAssistanteMaternelle(amUserId);
|
||||
}
|
||||
return _amModelFromBody(response.body);
|
||||
}
|
||||
|
||||
static AssistanteMaternelleModel _amModelFromBody(String body) {
|
||||
final decoded = jsonDecode(body);
|
||||
return AssistanteMaternelleModel.fromJson(
|
||||
Map<String, dynamic>.from(decoded is Map ? decoded : {}),
|
||||
);
|
||||
}
|
||||
|
||||
// Récupérer la liste des administrateurs (via /users filtré ou autre)
|
||||
|
||||
Reference in New Issue
Block a user