Merge master into develop - Synchronisation des branches

Fusion des travaux :
- Backend complet (ConfigService, DocumentsLegaux, Auth, etc.)
- Frontend étapes inscription 1-2
- Infrastructure Docker
- Documentation technique

Résolution des conflits :
- Images déplacées vers frontend/assets/images/
- Dossier Archives supprimé
- Backend : version master conservée
- Frontend : améliorations UI de develop conservées
This commit is contained in:
2026-01-27 14:56:49 +01:00
249 changed files with 33816 additions and 1449 deletions
@@ -0,0 +1,96 @@
import 'dart:io';
class ChildminderId {
String firstName;
String lastName;
String address;
String postalCode;
String city;
String phone;
String email;
String password;
File? profilePicture;
bool photoConsent;
ChildminderId({
this.firstName = '',
this.lastName = '',
this.address = '',
this.postalCode = '',
this.city = '',
this.phone = '',
this.email = '',
this.password = '',
this.profilePicture,
this.photoConsent = false,
});
}
class ChildminderProfessional {
String dateOfBirth;
String birthCity;
String birthCountry;
String socialSecurityNumber; // NIR
String agreementNumber;
int maxChildren;
ChildminderProfessional({
this.dateOfBirth = '',
this.birthCity = '',
this.birthCountry = '',
this.socialSecurityNumber = '',
this.agreementNumber = '',
this.maxChildren = 1,
});
}
class ChildminderRegistrationData {
ChildminderId identity;
ChildminderProfessional professional;
String presentationMessage;
bool cguAccepted;
bool isPhotoRequired;
ChildminderRegistrationData({
ChildminderId? identityData,
ChildminderProfessional? professionalData,
this.presentationMessage = '',
this.cguAccepted = false,
this.isPhotoRequired = false,
}) : identity = identityData ?? ChildminderId(),
professional = professionalData ?? ChildminderProfessional();
void updateIdentity(ChildminderId data) {
identity = data;
}
void updateProfessional(ChildminderProfessional data) {
professional = data;
}
void updatePresentation(String message) {
presentationMessage = message;
}
void acceptCGU() {
cguAccepted = true;
}
bool get isComplete {
return identity.firstName.isNotEmpty &&
identity.lastName.isNotEmpty &&
identity.address.isNotEmpty &&
identity.postalCode.isNotEmpty &&
identity.city.isNotEmpty &&
identity.phone.isNotEmpty &&
identity.email.isNotEmpty &&
identity.password.isNotEmpty &&
professional.dateOfBirth.isNotEmpty &&
professional.birthCity.isNotEmpty &&
professional.birthCountry.isNotEmpty &&
professional.socialSecurityNumber.isNotEmpty &&
professional.agreementNumber.isNotEmpty &&
cguAccepted &&
(!isPhotoRequired || (identity.profilePicture != null && identity.photoConsent));
}
}
@@ -0,0 +1,51 @@
class AssistantModel {
final String id;
final String firstName;
final String lastName;
final String? photoUrl;
final double hourlyRate;
final double dailyFees;
final AssistantStatus status;
final String? address;
final String? phone;
final String? email;
AssistantModel({
required this.id,
required this.firstName,
required this.lastName,
this.photoUrl,
required this.hourlyRate,
required this.dailyFees,
required this.status,
this.address,
this.phone,
this.email,
});
factory AssistantModel.fromJson(Map<String, dynamic> json) {
return AssistantModel(
id: json['id'],
firstName: json['firstName'],
lastName: json['lastName'],
photoUrl: json['photoUrl'],
hourlyRate: json['hourlyRate'].toDouble(),
dailyFees: json['dailyFees'].toDouble(),
status: AssistantStatus.values.byName(json['status']),
address: json['address'],
phone: json['phone'],
email: json['email'],
);
}
String get fullName => '$firstName $lastName';
String get hourlyRateFormatted => '${hourlyRate.toStringAsFixed(2)} €/h';
String get dailyFeesFormatted => '${dailyFees.toStringAsFixed(2)} €/jour';
}
enum AssistantStatus {
available,
busy,
onHoliday,
unavailable,
}
@@ -0,0 +1,58 @@
class ChildModel {
final String id;
final String firstName;
final String? lastName;
final String? photoUrl;
final DateTime birthDate;
final ChildStatus status;
final String? assistantId;
ChildModel({
required this.id,
required this.firstName,
this.lastName,
this.photoUrl,
required this.birthDate,
required this.status,
this.assistantId,
});
factory ChildModel.fromJson(Map<String, dynamic> json) {
return ChildModel(
id: json['id'],
firstName: json['firstName'],
lastName: json['lastName'],
photoUrl: json['photoUrl'],
birthDate: DateTime.parse(json['birthDate']),
status: ChildStatus.values.byName(json['status']),
assistantId: json['assistantId'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'firstName': firstName,
'lastName': lastName,
'photoUrl': photoUrl,
'birthDate': birthDate.toIso8601String(),
'status': status.name,
'assistantId': assistantId,
};
}
String get fullName => lastName != null ? '$firstName $lastName' : firstName;
int get ageInMonths {
final now = DateTime.now();
return (now.year - birthDate.year) * 12 + (now.month - birthDate.month);
}
}
enum ChildStatus {
withAssistant, // En garde chez l'assistante
available, // Disponible
onHoliday, // En vacances
sick, // Malade
searching, // Recherche d'assistante
}
@@ -0,0 +1,65 @@
class ContractModel {
final String id;
final String childId;
final String assistantId;
final ContractStatus status;
final DateTime startDate;
final DateTime? endDate;
final double hourlyRate;
final Map<String, dynamic>? terms;
final DateTime createdAt;
final DateTime? signedAt;
ContractModel({
required this.id,
required this.childId,
required this.assistantId,
required this.status,
required this.startDate,
this.endDate,
required this.hourlyRate,
this.terms,
required this.createdAt,
this.signedAt,
});
factory ContractModel.fromJson(Map<String, dynamic> json) {
return ContractModel(
id: json['id'],
childId: json['childId'],
assistantId: json['assistantId'],
status: ContractStatus.values.byName(json['status']),
startDate: DateTime.parse(json['startDate']),
endDate: json['endDate'] != null ? DateTime.parse(json['endDate']) : null,
hourlyRate: json['hourlyRate'].toDouble(),
terms: json['terms'],
createdAt: DateTime.parse(json['createdAt']),
signedAt: json['signedAt'] != null ? DateTime.parse(json['signedAt']) : null,
);
}
bool get isActive => status == ContractStatus.active;
bool get needsSignature => status == ContractStatus.draft;
String get statusLabel {
switch (status) {
case ContractStatus.draft:
return 'Brouillon';
case ContractStatus.pending:
return 'En attente de validation';
case ContractStatus.active:
return 'En cours';
case ContractStatus.ended:
return 'Terminé';
case ContractStatus.cancelled:
return 'Annulé';
}
}
}
enum ContractStatus {
draft,
pending,
active,
ended,
cancelled,
}
@@ -0,0 +1,46 @@
class ConversationModel {
final String id;
final String title;
final List<String> participantIds;
final List<MessageModel> messages;
final DateTime lastMessageAt;
final int unreadCount;
final String? childId;
ConversationModel({
required this.id,
required this.title,
required this.participantIds,
required this.messages,
required this.lastMessageAt,
this.unreadCount = 0,
this.childId,
});
MessageModel? get lastMessage => messages.isNotEmpty ? messages.last : null;
bool get hasUnreadMessages => unreadCount > 0;
}
class MessageModel {
final String id;
final String content;
final String senderId;
final DateTime sentAt;
final bool isFromAI;
final MessageStatus status;
MessageModel({
required this.id,
required this.content,
required this.senderId,
required this.sentAt,
this.isFromAI = false,
required this.status,
});
}
enum MessageStatus {
sent,
delivered,
read,
}
@@ -0,0 +1,66 @@
class EventModel {
final String id;
final String title;
final String? description;
final DateTime startDate;
final DateTime? endDate;
final EventType type;
final EventStatus status;
final String? childId;
final String? assistantId;
final String? createdBy;
EventModel({
required this.id,
required this.title,
this.description,
required this.startDate,
this.endDate,
required this.type,
required this.status,
this.childId,
this.assistantId,
this.createdBy,
});
factory EventModel.fromJson(Map<String, dynamic> json) {
return EventModel(
id: json['id'],
title: json['title'],
description: json['description'],
startDate: DateTime.parse(json['startDate']),
endDate: json['endDate'] != null ? DateTime.parse(json['endDate']) : null,
type: EventType.values.byName(json['type']),
status: EventStatus.values.byName(json['status']),
childId: json['childId'],
assistantId: json['assistantId'],
createdBy: json['createdBy'],
);
}
bool get isMultiDay => endDate != null && !isSameDay(startDate, endDate!);
bool get isPending => status == EventStatus.pending;
bool get needsConfirmation => isPending && createdBy != 'current_user';
static bool isSameDay(DateTime date1, DateTime date2) {
return date1.year == date2.year &&
date1.month == date2.month &&
date1.day == date2.day;
}
}
enum EventType {
parentVacation, // Vacances parents
childAbsence, // Absence enfant
rpeActivity, // Activité RPE
assistantVacation, // Congés assistante maternelle
sickLeave, // Arrêt maladie
personalNote, // Note personnelle
}
enum EventStatus {
confirmed,
pending,
refused,
cancelled,
}
@@ -0,0 +1,42 @@
class NotificationModel {
final String id;
final String title;
final String content;
final NotificationType type;
final DateTime createdAt;
final bool isRead;
final String? actionUrl;
final Map<String, dynamic>? metadata;
NotificationModel({
required this.id,
required this.title,
required this.content,
required this.type,
required this.createdAt,
this.isRead = false,
this.actionUrl,
this.metadata,
});
factory NotificationModel.fromJson(Map<String, dynamic> json) {
return NotificationModel(
id: json['id'],
title: json['title'],
content: json['content'],
type: NotificationType.values.byName(json['type']),
createdAt: DateTime.parse(json['createdAt']),
isRead: json['isRead'] ?? false,
actionUrl: json['actionUrl'],
metadata: json['metadata'],
);
}
}
enum NotificationType {
newEvent, // Nouvel événement
fileModified, // Dossier modifié
contractPending, // Contrat en attente
paymentPending, // Paiement en attente
unreadMessage, // Message non lu
}
@@ -0,0 +1,97 @@
import 'dart:io'; // Pour File
import '../models/card_assets.dart'; // Import de l'enum CardColorVertical
class ParentData {
String firstName;
String lastName;
String address; // Rue et numéro
String postalCode; // Ajout
String city; // Ajout
String phone;
String email;
String password; // Peut-être pas nécessaire pour le récap, mais pour la création initiale si
File? profilePicture; // Chemin ou objet File
ParentData({
this.firstName = '',
this.lastName = '',
this.address = '', // Rue
this.postalCode = '', // Ajout
this.city = '', // Ajout
this.phone = '',
this.email = '',
this.password = '',
this.profilePicture,
});
}
class ChildData {
String firstName;
String lastName;
String dob; // Date de naissance ou prévisionnelle
bool photoConsent;
bool multipleBirth;
bool isUnbornChild;
File? imageFile;
CardColorVertical cardColor; // Nouveau champ pour la couleur de la carte
ChildData({
this.firstName = '',
this.lastName = '',
this.dob = '',
this.photoConsent = false,
this.multipleBirth = false,
this.isUnbornChild = false,
this.imageFile,
required this.cardColor, // Rendre requis dans le constructeur
});
}
class UserRegistrationData {
ParentData parent1;
ParentData? parent2; // Optionnel
List<ChildData> children;
String motivationText;
bool cguAccepted;
UserRegistrationData({
ParentData? parent1Data,
this.parent2,
List<ChildData>? childrenData,
this.motivationText = '',
this.cguAccepted = false,
}) : parent1 = parent1Data ?? ParentData(),
children = childrenData ?? [];
// Méthode pour ajouter/mettre à jour le parent 1
void updateParent1(ParentData data) {
parent1 = data;
}
// Méthode pour ajouter/mettre à jour le parent 2
void updateParent2(ParentData? data) {
parent2 = data;
}
// Méthode pour ajouter un enfant
void addChild(ChildData child) {
children.add(child);
}
// Méthode pour mettre à jour un enfant (si nécessaire plus tard)
void updateChild(int index, ChildData child) {
if (index >= 0 && index < children.length) {
children[index] = child;
}
}
// Mettre à jour la motivation
void updateMotivation(String text) {
motivationText = text;
}
// Accepter les CGU
void acceptCGU() {
cguAccepted = true;
}
}