feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -101,10 +101,10 @@ class PassageRepository extends ChangeNotifier {
|
||||
return _passageBox.values.where((passage) => passage.fkOperation == operationId).toList();
|
||||
}
|
||||
|
||||
// Récupérer les passages par utilisateur
|
||||
List<PassageModel> getPassagesByUser(int userId) {
|
||||
// Récupérer les passages par utilisateur (ope_users.id)
|
||||
List<PassageModel> getPassagesByUser(int opeUserId) {
|
||||
try {
|
||||
return _passageBox.values.where((passage) => passage.fkUser == userId).toList();
|
||||
return _passageBox.values.where((passage) => passage.fkUser == opeUserId).toList();
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors de la récupération des passages par utilisateur: $e');
|
||||
return [];
|
||||
@@ -380,12 +380,12 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Vérifier si la requête a été mise en file d'attente
|
||||
if (response.data['queued'] == true) {
|
||||
// Récupérer l'utilisateur actuel
|
||||
final currentUserId = CurrentUserService.instance.userId;
|
||||
// Récupérer l'utilisateur actuel (ope_users.id)
|
||||
final currentOpeUserId = CurrentUserService.instance.opeUserId;
|
||||
|
||||
// Mode offline : mettre à jour localement et marquer comme non synchronisé
|
||||
final offlinePassage = passage.copyWith(
|
||||
fkUser: currentUserId, // Le passage appartient maintenant à l'utilisateur qui l'a modifié
|
||||
fkUser: currentOpeUserId, // Le passage appartient à l'utilisateur qui l'a modifié (ope_users.id)
|
||||
lastSyncedAt: null,
|
||||
isSynced: false,
|
||||
);
|
||||
@@ -418,12 +418,12 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Mode online : traitement normal
|
||||
if (response.statusCode == 200) {
|
||||
// Récupérer l'utilisateur actuel
|
||||
final currentUserId = CurrentUserService.instance.userId;
|
||||
// Récupérer l'utilisateur actuel (ope_users.id)
|
||||
final currentOpeUserId = CurrentUserService.instance.opeUserId;
|
||||
|
||||
// Mettre à jour le passage localement avec le user actuel
|
||||
final updatedPassage = passage.copyWith(
|
||||
fkUser: currentUserId, // Le passage appartient maintenant à l'utilisateur qui l'a modifié
|
||||
fkUser: currentOpeUserId, // Le passage appartient à l'utilisateur qui l'a modifié (ope_users.id)
|
||||
lastSyncedAt: DateTime.now(),
|
||||
isSynced: true,
|
||||
);
|
||||
@@ -574,10 +574,10 @@ class PassageRepository extends ChangeNotifier {
|
||||
|
||||
// Calculer les statistiques pour chaque utilisateur
|
||||
for (final entry in passagesByUser.entries) {
|
||||
final userId = entry.key;
|
||||
final opeUserId = entry.key; // ID de l'utilisateur dans ope_users
|
||||
final userPassages = entry.value;
|
||||
|
||||
statsByUser[userId] = {
|
||||
statsByUser[opeUserId] = {
|
||||
'total': userPassages.length,
|
||||
'effectues': userPassages.where((p) => p.fkType == 1).length,
|
||||
'a_finaliser': userPassages.where((p) => p.fkType == 2).length,
|
||||
|
||||
@@ -301,17 +301,20 @@ class SectorRepository extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Mettre à jour un secteur via l'API
|
||||
Future<Map<String, dynamic>> updateSector(SectorModel sector, {List<int>? users}) async {
|
||||
Future<Map<String, dynamic>> updateSector(SectorModel sector, {List<int>? users, int? chkAdressesChange}) async {
|
||||
try {
|
||||
// Préparer les données à envoyer
|
||||
final Map<String, dynamic> requestData = {
|
||||
...sector.toJson(),
|
||||
};
|
||||
|
||||
|
||||
// Ajouter les utilisateurs si fournis
|
||||
if (users != null) {
|
||||
requestData['users'] = users;
|
||||
}
|
||||
|
||||
// Ajouter le paramètre chk_adresses_change (par défaut 1 si non spécifié)
|
||||
requestData['chk_adresses_change'] = chkAdressesChange ?? 1;
|
||||
|
||||
final response = await ApiService.instance.put(
|
||||
'${AppKeys.sectorsEndpoint}/${sector.id}',
|
||||
@@ -339,19 +342,19 @@ class SectorRepository extends ChangeNotifier {
|
||||
final SectorModel updatedSector = SectorModel.fromJson(responseData['sector']);
|
||||
await saveSector(updatedSector);
|
||||
}
|
||||
|
||||
// Traiter les passages retournés s'ils existent
|
||||
if (responseData['passages_sector'] != null) {
|
||||
|
||||
// Traiter les passages retournés UNIQUEMENT si chk_adresses_change = 1
|
||||
if ((chkAdressesChange ?? 1) == 1 && responseData['passages_sector'] != null) {
|
||||
try {
|
||||
final passagesData = responseData['passages_sector'] as List<dynamic>;
|
||||
debugPrint('Traitement de ${passagesData.length} passages après UPDATE');
|
||||
|
||||
debugPrint('Traitement de ${passagesData.length} passages après UPDATE (chk_adresses_change=1)');
|
||||
|
||||
// Utiliser PassageRepository pour traiter les passages
|
||||
final passageRepository = PassageRepository();
|
||||
|
||||
|
||||
// Vider d'abord tous les passages du secteur
|
||||
await _deleteAllPassagesOfSector(sector.id);
|
||||
|
||||
|
||||
// Puis sauvegarder tous les passages retournés
|
||||
final List<PassageModel> passagesToSave = [];
|
||||
for (final passageData in passagesData) {
|
||||
@@ -363,7 +366,7 @@ class SectorRepository extends ChangeNotifier {
|
||||
debugPrint('Erreur lors du traitement d\'un passage: $e');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (passagesToSave.isNotEmpty) {
|
||||
await passageRepository.savePassages(passagesToSave);
|
||||
debugPrint('${passagesToSave.length} passages sauvegardés après UPDATE');
|
||||
@@ -371,6 +374,8 @@ class SectorRepository extends ChangeNotifier {
|
||||
} catch (e) {
|
||||
debugPrint('Erreur lors du traitement des passages: $e');
|
||||
}
|
||||
} else if ((chkAdressesChange ?? 1) == 0) {
|
||||
debugPrint('⏭️ Passages ignorés (chk_adresses_change=0) - les passages existants sont conservés');
|
||||
}
|
||||
|
||||
// Traiter les users_sectors retournés s'ils existent
|
||||
|
||||
@@ -646,8 +646,8 @@ class UserRepository extends ChangeNotifier {
|
||||
// === SYNCHRONISATION ET REFRESH ===
|
||||
|
||||
/// Rafraîchir la session (soft login)
|
||||
/// Utilise un refresh partiel si la dernière sync date de moins de 24h
|
||||
/// Sinon fait un refresh complet
|
||||
/// NOTE: Les endpoints /session/refresh/all et /session/refresh/partial ont été retirés
|
||||
/// Cette méthode maintient la session locale sans faire d'appel API
|
||||
Future<bool> refreshSession() async {
|
||||
try {
|
||||
debugPrint('🔄 Début du refresh de session...');
|
||||
@@ -658,7 +658,7 @@ class UserRepository extends ChangeNotifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOUVEAU : Vérifier la connexion internet avant de faire des appels API
|
||||
// Vérifier la connexion internet avant de faire des appels API
|
||||
final hasConnection = await ApiService.instance.hasInternetConnection();
|
||||
if (!hasConnection) {
|
||||
debugPrint('📵 Pas de connexion internet - refresh annulé');
|
||||
@@ -671,147 +671,17 @@ class UserRepository extends ChangeNotifier {
|
||||
_startAutoRefreshTimer();
|
||||
}
|
||||
|
||||
// Récupérer la dernière date de sync depuis settings
|
||||
DateTime? lastSync;
|
||||
try {
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
final lastSyncString = settingsBox.get('last_sync') as String?;
|
||||
if (lastSyncString != null) {
|
||||
lastSync = DateTime.parse(lastSyncString);
|
||||
debugPrint('📅 Dernière sync: ${lastSync.toIso8601String()}');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur lecture last_sync: $e');
|
||||
}
|
||||
// NOTE: Les endpoints de refresh ont été retirés
|
||||
// La session locale est maintenue mais aucune synchronisation avec le serveur n'est effectuée
|
||||
debugPrint('ℹ️ Refresh de session désactivé (endpoints retirés)');
|
||||
|
||||
// Déterminer si on fait un refresh partiel ou complet
|
||||
// Refresh partiel si:
|
||||
// - On a une date de dernière sync
|
||||
// - Cette date est de moins de 24h
|
||||
final now = DateTime.now();
|
||||
final shouldPartialRefresh = lastSync != null &&
|
||||
now.difference(lastSync).inHours < 24;
|
||||
|
||||
if (shouldPartialRefresh) {
|
||||
debugPrint('⚡ Refresh partiel (dernière sync < 24h)');
|
||||
|
||||
try {
|
||||
// Appel API pour refresh partiel
|
||||
final response = await ApiService.instance.refreshSessionPartial(lastSync);
|
||||
|
||||
if (response.data != null && response.data['status'] == 'success') {
|
||||
// Traiter uniquement les données modifiées
|
||||
await _processPartialRefreshData(response.data);
|
||||
|
||||
// Mettre à jour last_sync
|
||||
await _saveLastSyncTimestamp(now);
|
||||
|
||||
debugPrint('✅ Refresh partiel réussi');
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ Erreur refresh partiel: $e');
|
||||
|
||||
// Vérifier si c'est une erreur d'authentification
|
||||
if (_isAuthenticationError(e)) {
|
||||
debugPrint('🔒 Erreur d\'authentification détectée - nettoyage de la session locale');
|
||||
await _clearInvalidSession();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sinon, on tente un refresh complet
|
||||
debugPrint('Tentative de refresh complet...');
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh complet
|
||||
debugPrint('🔄 Refresh complet des données...');
|
||||
|
||||
try {
|
||||
final response = await ApiService.instance.refreshSessionAll();
|
||||
|
||||
if (response.data != null && response.data['status'] == 'success') {
|
||||
// Traiter toutes les données comme un login
|
||||
await DataLoadingService.instance.processLoginData(response.data);
|
||||
|
||||
// Mettre à jour last_sync
|
||||
await _saveLastSyncTimestamp(now);
|
||||
|
||||
debugPrint('✅ Refresh complet réussi');
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur refresh complet: $e');
|
||||
|
||||
// Vérifier si c'est une erreur d'authentification
|
||||
if (_isAuthenticationError(e)) {
|
||||
debugPrint('🔒 Session invalide côté serveur - nettoyage de la session locale');
|
||||
await _clearInvalidSession();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur générale refresh session: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Traiter les données d'un refresh partiel
|
||||
Future<void> _processPartialRefreshData(Map<String, dynamic> data) async {
|
||||
try {
|
||||
debugPrint('📦 Traitement des données partielles...');
|
||||
|
||||
// Traiter les secteurs modifiés
|
||||
if (data['sectors'] != null && data['sectors'] is List) {
|
||||
final sectorsBox = Hive.box<SectorModel>(AppKeys.sectorsBoxName);
|
||||
for (final sectorData in data['sectors']) {
|
||||
final sector = SectorModel.fromJson(sectorData);
|
||||
await sectorsBox.put(sector.id, sector);
|
||||
}
|
||||
debugPrint('✅ ${data['sectors'].length} secteurs mis à jour');
|
||||
}
|
||||
|
||||
// Traiter les passages modifiés
|
||||
if (data['passages'] != null && data['passages'] is List) {
|
||||
final passagesBox = Hive.box<PassageModel>(AppKeys.passagesBoxName);
|
||||
for (final passageData in data['passages']) {
|
||||
final passage = PassageModel.fromJson(passageData);
|
||||
await passagesBox.put(passage.id, passage);
|
||||
}
|
||||
debugPrint('✅ ${data['passages'].length} passages mis à jour');
|
||||
}
|
||||
|
||||
// Traiter les opérations modifiées
|
||||
if (data['operations'] != null && data['operations'] is List) {
|
||||
final operationsBox = Hive.box<OperationModel>(AppKeys.operationsBoxName);
|
||||
for (final operationData in data['operations']) {
|
||||
final operation = OperationModel.fromJson(operationData);
|
||||
await operationsBox.put(operation.id, operation);
|
||||
}
|
||||
debugPrint('✅ ${data['operations'].length} opérations mises à jour');
|
||||
}
|
||||
|
||||
// Traiter les membres modifiés
|
||||
if (data['membres'] != null && data['membres'] is List) {
|
||||
final membresBox = Hive.box<MembreModel>(AppKeys.membresBoxName);
|
||||
for (final membreData in data['membres']) {
|
||||
final membre = MembreModel.fromJson(membreData);
|
||||
await membresBox.put(membre.id, membre);
|
||||
}
|
||||
debugPrint('✅ ${data['membres'].length} membres mis à jour');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur traitement données partielles: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// Sauvegarder le timestamp de la dernière sync
|
||||
Future<void> _saveLastSyncTimestamp(DateTime timestamp) async {
|
||||
try {
|
||||
@@ -825,55 +695,6 @@ class UserRepository extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si l'erreur est une erreur d'authentification (401, 403)
|
||||
/// Retourne false pour les erreurs 404 (route non trouvée)
|
||||
bool _isAuthenticationError(dynamic error) {
|
||||
final errorMessage = error.toString().toLowerCase();
|
||||
|
||||
// Si c'est une erreur 404, ce n'est pas une erreur d'authentification
|
||||
// C'est juste que la route n'existe pas encore côté API
|
||||
if (errorMessage.contains('404') || errorMessage.contains('not found')) {
|
||||
debugPrint('⚠️ Route API non trouvée (404) - en attente de l\'implémentation côté serveur');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier les vraies erreurs d'authentification
|
||||
return errorMessage.contains('401') ||
|
||||
errorMessage.contains('403') ||
|
||||
errorMessage.contains('unauthorized') ||
|
||||
errorMessage.contains('forbidden') ||
|
||||
errorMessage.contains('session expired') ||
|
||||
errorMessage.contains('authentication failed');
|
||||
}
|
||||
|
||||
/// Nettoie la session locale invalide
|
||||
Future<void> _clearInvalidSession() async {
|
||||
try {
|
||||
debugPrint('🗑️ Nettoyage de la session invalide...');
|
||||
|
||||
// Arrêter le timer de refresh
|
||||
_stopAutoRefreshTimer();
|
||||
|
||||
// Nettoyer les données de session
|
||||
await CurrentUserService.instance.clearUser();
|
||||
await CurrentAmicaleService.instance.clearAmicale();
|
||||
|
||||
// Nettoyer les IDs dans settings
|
||||
if (Hive.isBoxOpen(AppKeys.settingsBoxName)) {
|
||||
final settingsBox = Hive.box(AppKeys.settingsBoxName);
|
||||
await settingsBox.delete('current_user_id');
|
||||
await settingsBox.delete('current_amicale_id');
|
||||
await settingsBox.delete('last_sync');
|
||||
}
|
||||
|
||||
// Supprimer le sessionId de l'API
|
||||
ApiService.instance.setSessionId(null);
|
||||
|
||||
debugPrint('✅ Session locale nettoyée suite à erreur d\'authentification');
|
||||
} catch (e) {
|
||||
debugPrint('❌ Erreur lors du nettoyage de session: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// === TIMER DE REFRESH AUTOMATIQUE ===
|
||||
|
||||
@@ -1078,8 +899,14 @@ class UserRepository extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Convertir ope_user_id en int si présent
|
||||
final dynamic rawOpeUserId = userData['ope_user_id'];
|
||||
final int? opeUserId = rawOpeUserId != null
|
||||
? (rawOpeUserId is String ? int.parse(rawOpeUserId) : rawOpeUserId as int)
|
||||
: null;
|
||||
|
||||
debugPrint(
|
||||
'✅ Données traitées - id: $id, role: $role, fkEntite: $fkEntite');
|
||||
'✅ Données traitées - id: $id, role: $role, fkEntite: $fkEntite, opeUserId: $opeUserId');
|
||||
|
||||
// Créer un utilisateur avec toutes les données disponibles
|
||||
return UserModel(
|
||||
@@ -1103,6 +930,7 @@ class UserRepository extends ChangeNotifier {
|
||||
mobile: userData['mobile'],
|
||||
dateNaissance: dateNaissance,
|
||||
dateEmbauche: dateEmbauche,
|
||||
opeUserId: opeUserId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user