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:
pierre
2025-11-09 18:26:27 +01:00
parent 21657a3820
commit 2f5946a184
812 changed files with 142105 additions and 25992 deletions

View File

@@ -828,6 +828,7 @@ if (kIsWeb)
// Détection basée sur l'URL
if (currentUrl.contains('dapp.geosector.fr')) DEV
if (currentUrl.contains('rapp.geosector.fr')) REC
if (currentUrl.contains('app3.geosector.fr')) PROD
Sinon PROD
```

View File

@@ -124,8 +124,8 @@ if (amicale == null || amicale.id == 0) {
"city": "Paris",
"country": "FR"
},
"url": "https://app.geosector.fr/stripe/return",
"refresh_url": "https://app.geosector.fr/stripe/refresh"
"url": "https://app3.geosector.fr/stripe/return",
"refresh_url": "https://app3.geosector.fr/stripe/refresh"
}
```
@@ -148,8 +148,8 @@ $account = \Stripe\Account::create([
// 2. Création du lien d'onboarding
$onboardingLink = \Stripe\AccountLink::create([
'account' => $account->id,
'refresh_url' => 'https://app.geosector.fr/stripe/refresh',
'return_url' => 'https://app.geosector.fr/stripe/return',
'refresh_url' => 'https://app3.geosector.fr/stripe/refresh',
'return_url' => 'https://app3.geosector.fr/stripe/return',
'type' => 'account_onboarding'
]);
@@ -529,6 +529,378 @@ $paymentIntent = \Stripe\PaymentIntent::create([
---
## 📱 FLOW PAIEMENT QR CODE (Web + Mobile)
### 🎯 Vue d'ensemble
Le paiement par QR Code permet aux clients de payer directement avec leur téléphone en scannant un code QR généré par l'application. Cette méthode fonctionne sur **Web et Mobile** et ne nécessite pas de matériel spécifique.
### 🔄 Diagramme de séquence complet
```
┌─────────────┐ ┌─────────────┐ ┌──────────┐ ┌─────────┐ ┌─────────┐
│ App Flutter │ │ API PHP │ │ Stripe │ │ Client │ │ Passage │
└──────┬──────┘ └──────┬──────┘ └────┬─────┘ └────┬────┘ └────┬────┘
│ │ │ │ │
[1] │ Validation form │ │ │ │
│ + montant CB │ │ │ │
│ │ │ │ │
[2] │ POST/PUT passage │ │ │ │
│──────────────────>│ │ │ │
│ │ │ │ │
[3] │<──────────────────│ │ │ │
│ Passage ID: 456 │ │ │ │
│ │ │ │ │
[4] │ Vérif Stripe │ │ │ │
│ chkStripe=true │ │ │ │
│ + stripeId rempli │ │ │ │
│ │ │ │ │
[5] │ Dialog Sélection │ │ │ │
│ "Règlement CB" │ │ │ │
│ [QRCode|TapToPay] │ │ │ │
│ │ │ │ │
[6] │ Clic "QR Code" │ │ │ │
│ │ │ │ │
[7] │ POST payment-links│ │ │ │
│──────────────────>│ (passage_id: 456)│ │ │
│ │ │ │ │
[8] │ │ Create PaymentLink │ │
│ │─────────────────>│ │ │
│ │ │ │ │
[9] │ │<─────────────────│ │ │
│ │ link_id + url │ │ │
│ │ │ │ │
[10] │<──────────────────│ │ │ │
│ PaymentLink data │ │ │ │
│ │ │ │ │
[11] │ Génération QR │ │ │ │
│ avec URL Stripe │ │ │ │
│ │ │ │ │
[12] │ Affichage dialog │ │ │ │
│ QR Code │ │ │ │
│ ┌──────────────┐ │ │ │ │
│ │ QR Code │ │ │ │ │
│ │ ▓▓▓▓▓▓▓▓ │ │ │ │ │
│ │ 20.00 € │ │ │ │ │
│ └──────────────┘ │ │ │ │
│ │ │ │ │
[13] │ │ │ Scan QR Code │ │
│ │ │<───────────────│ │
│ │ │ │ │
[14] │ │ │ Page paiement │ │
│ │ │───────────────>│ │
│ │ │ Stripe hosted │ │
│ │ │ │ │
[15] │ │ │ Saisie CB │ │
│ │ │<───────────────│ │
│ │ │ │ │
[16] │ │ │ Validation │ │
│ │ │───────────────>│ │
│ │ │ │ │
[17] │ │ Webhook │ │ │
│ │<─────────────────│ │ │
│ │ payment_succeeded│ │ │
│ │ │ │ │
[18] │ │ Update passage │ │ │
│ │────────────────────────────────────────────────>│
│ │ stripe_payment_id│ │ │
│ │ │ │ │
[19] │ │ │ Confirmation │ │
│ │ │───────────────>│ │
│ │ │ "Merci!" │ │
```
### 📋 Détail des étapes
#### Étape 1-3 : SAUVEGARDE DU PASSAGE
**Identique au flow Tap to Pay** - Le passage est toujours créé en premier pour obtenir un ID réel.
#### Étape 4 : VÉRIFICATION STRIPE
**Acteur:** Application Flutter
**Conditions vérifiées:**
```dart
final amicale = CurrentAmicaleService.instance.currentAmicale;
final stripeEnabled = amicale?.chkStripe == true &&
amicale?.stripeId != null &&
amicale!.stripeId.isNotEmpty;
if (stripeEnabled && fkTypeReglement == 3 && montant > 0) {
// Afficher dialog de sélection de méthode
}
```
#### Étape 5 : DIALOG DE SÉLECTION DE MÉTHODE
**Widget:** `PaymentMethodSelectionDialog`
**Interface affichée:**
```
┌────────────────────────────────┐
│ Règlement CB │
│ │
│ 👤 Jean Dupont │
│ 💰 20.00 € │
│ │
│ Sélectionnez une méthode : │
│ │
│ ┌──────────────────────────┐ │
│ │ 📱 Paiement par QR Code │ │
│ │ Le client scanne le code │ │
│ └──────────────────────────┘ │
│ │
│ ┌──────────────────────────┐ │ (Si compatible)
│ │ 💳 Tap to Pay │ │
│ │ Paiement sans contact │ │
│ └──────────────────────────┘ │
│ │
│ 🔒 Sécurisé par Stripe │
└────────────────────────────────┘
```
#### Étape 6-10 : CRÉATION DU PAYMENT LINK
**Requête:** `POST /api/stripe/payment-links`
**Payload:**
```json
{
"amount": 2000,
"currency": "eur",
"description": "Calendrier pompiers - Jean Dupont",
"passage_id": 456,
"metadata": {
"passage_id": "456",
"habitant_name": "Jean Dupont",
"adresse": "10 Rue de la Paix, Paris"
}
}
```
**Réponse:**
```json
{
"success": true,
"payment_link_id": "plink_1234567890",
"url": "https://buy.stripe.com/test_xxxxxxxxxxxxx",
"amount": 2000,
"passage_id": 456
}
```
**Code PHP Backend:**
```php
$paymentLink = \Stripe\PaymentLink::create([
'line_items' => [[
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => 'Calendrier pompiers',
],
'unit_amount' => 2000,
],
'quantity' => 1,
]],
'metadata' => [
'passage_id' => '456',
'type' => 'qr_code_payment',
],
'after_completion' => [
'type' => 'hosted_confirmation',
'hosted_confirmation' => [
'custom_message' => 'Merci pour votre paiement !',
],
],
], [
'stripe_account' => $amicale->stripe_id,
]);
```
#### Étape 11-12 : AFFICHAGE DU QR CODE
**Widget:** `QRCodePaymentDialog`
**Interface:**
```
┌──────────────────────────────┐
│ Paiement par QR Code │
│ │
│ 💰 20.00 € │
│ │
│ ┌────────────────────────┐ │
│ │ │ │
│ │ ▓▓▓▓▓ ▓▓▓▓▓▓▓ │ │
│ │ ▓▓▓▓▓ ▓▓ ▓▓ │ │
│ │ ▓▓▓▓▓ ▓▓▓▓▓▓▓ │ │
│ │ QR CODE HERE │ │
│ │ │ │
│ └────────────────────────┘ │
│ │
│ Scannez ce QR code avec │
│ votre téléphone │
│ │
│ Vous serez redirigé vers │
│ une page sécurisée Stripe │
│ │
│ 🔒 Paiement sécurisé │
│ │
│ [Fermer] │
└──────────────────────────────┘
```
#### Étape 13-16 : PAIEMENT CLIENT
**Acteur:** Client final
**Actions:**
1. Scan du QR Code avec son smartphone
2. Ouverture automatique de l'URL Stripe dans le navigateur
3. Affichage de la page de paiement Stripe hébergée
4. Saisie des informations de carte bancaire
5. Validation 3D Secure si nécessaire
6. Confirmation du paiement
#### Étape 17-18 : WEBHOOK ET MISE À JOUR
**Requête Webhook:** `POST /api/stripe/webhook`
**Event:** `checkout.session.completed` ou `payment_intent.succeeded`
**Payload Stripe:**
```json
{
"type": "payment_intent.succeeded",
"data": {
"object": {
"id": "pi_1234567890",
"amount": 2000,
"metadata": {
"passage_id": "456"
}
}
}
}
```
**Action Backend:**
```php
// Récupérer le passage
$passage = Passage::find($paymentIntent->metadata->passage_id);
// Mettre à jour avec le payment ID
$passage->stripe_payment_id = $paymentIntent->id;
$passage->save();
Log::info('Payment confirmed via QR Code', [
'passage_id' => $passage->id,
'payment_id' => $paymentIntent->id,
]);
```
### 🔄 Comparaison QR Code vs Tap to Pay
| Aspect | QR Code | Tap to Pay |
|--------|---------|------------|
| **Plateformes** | Web + Mobile | Mobile uniquement |
| **Matériel requis** | Aucun | NFC (iPhone XS+ / Android certifié) |
| **Client utilise** | Son propre téléphone | Le téléphone du pompier |
| **payment_method_types** | `["card"]` | `["card_present"]` |
| **Interface paiement** | Page Stripe hébergée | NFC sur l'app |
| **Délai confirmation** | Immédiat (webhook) | Immédiat (SDK) |
| **Expérience client** | Scan + saisie CB | Approche carte |
| **Sans contact physique** | ✅ Oui (COVID-safe) | ❌ Non (proximité requise) |
| **Montant minimum** | 0.50€ | 1.00€ |
| **Cas d'usage idéal** | Client à distance | Face à face |
### ✅ Conditions d'éligibilité
#### Affichage du bouton "QR Code"
```dart
// Conditions cumulatives :
final canShowQRCode =
amicale.chkStripe == true && // Stripe activé
amicale.stripeId.isNotEmpty && // Compte configuré
fkTypeReglement == 3 && // CB sélectionnée
montant > 0; // Montant valide
```
#### Différences avec Tap to Pay
- **Pas besoin de** `stripeLocationId` (spécifique Terminal)
- **Pas de vérification** device (fonctionne partout)
- **Pas de batterie** minimum requise
### 🎨 Widgets créés
#### 1. PaymentMethodSelectionDialog
**Fichier:** `lib/presentation/widgets/payment_method_selection_dialog.dart`
**Rôle:** Choisir entre QR Code et Tap to Pay
**Props:**
```dart
PaymentMethodSelectionDialog({
required PassageModel passage,
required double amount,
required String habitantName,
required StripeConnectService stripeConnectService,
VoidCallback? onTapToPaySelected,
})
```
#### 2. QRCodePaymentDialog
**Fichier:** `lib/presentation/widgets/qr_code_payment_dialog.dart`
**Rôle:** Afficher le QR Code généré
**Props:**
```dart
QRCodePaymentDialog({
required PaymentLinkResult paymentLink,
VoidCallback? onClose,
})
```
### 🔧 Services modifiés
#### StripeConnectService
**Nouvelle méthode:**
```dart
Future<PaymentLinkResult?> createPaymentLink({
required int amountInCents,
required int passageId,
String? description,
Map<String, dynamic>? metadata,
})
```
### 📊 Modèles créés
#### PaymentLinkResult
**Fichier:** `lib/core/data/models/payment_link_result.dart`
```dart
class PaymentLinkResult {
final String paymentLinkId;
final String url;
final int amount;
final int? passageId;
}
```
### 🔐 Sécurité
#### Validation Backend
```php
// Vérifications obligatoires
$request->validate([
'amount' => 'required|integer|min:50',
'passage_id' => 'required|integer|exists:ope_pass,id',
]);
// Vérifier Stripe activé
if (!$amicale->chk_stripe || empty($amicale->stripe_id)) {
return response()->json(['error' => 'Stripe non activé'], 403);
}
// Créer sur le compte Connect de l'amicale
\Stripe\PaymentLink::create([...], [
'stripe_account' => $amicale->stripe_id,
]);
```
### 📱 Package ajouté
**pubspec.yaml:**
```yaml
dependencies:
qr_flutter: ^4.1.0 # Génération de QR codes
```
---
## 💻 FLOW PAIEMENT WEB
### 🔄 Principales différences avec Tap to Pay
@@ -846,8 +1218,9 @@ Log::info('PaymentIntent created', [
| 1.0 | 28/09/2025 | Création documentation initiale |
| 1.1 | 28/09/2025 | Ajout flow complet Tap to Pay |
| 1.2 | 28/09/2025 | Intégration passage_id et metadata |
| 1.3 | 05/11/2025 | Ajout flow paiement par QR Code |
---
*Document technique - Flow Stripe GEOSECTOR*
*Dernière mise à jour : 28 septembre 2025*
*Dernière mise à jour : 5 novembre 2025*

View File

@@ -1,24 +1,24 @@
# Flutter Analyze Report - GEOSECTOR App
📅 **Date de génération** : 05/10/2025 - 10:00
📅 **Date de génération** : 09/11/2025 - 10:49
🔍 **Analyse complète de l'application Flutter**
📱 **Version en cours** : 3.3.4 (Build 334 - Release)
📱 **Version en cours** : 3.3.6 (Build 336 - Release)
---
## 📊 Résumé Exécutif
- **Total des problèmes détectés** : 32 issues (⬇️ **-185 depuis l'analyse du 29/09** | -85% 🎉)
- **Temps d'analyse** : 0.7s
- **Total des problèmes détectés** : 30 issues (⬇️ **-2 depuis le 05/10** | -6% 🎉)
- **Temps d'analyse** : 1.0s
- **État global** : ✅ **EXCELLENT** - Tous les warnings éliminés !
### Distribution des problèmes
| Type | Nombre | Évolution (vs 29/09) | Sévérité | Action recommandée |
| Type | Nombre | Évolution (vs 05/10) | Sévérité | Action recommandée |
|------|--------|-----------------------|----------|-------------------|
| **Errors** | 0 | ✅ Stable (0) | 🔴 Critique | - |
| **Warnings** | 0 | ✅ **-16 (-100%)** 🎉 | 🟠 Important | ✅ **TERMINÉ** |
| **Info** | 32 | ⬇️ -169 (-84%) 🎉 | 🔵 Informatif | Optimisations mineures |
| **Warnings** | 0 | ✅ Stable (0) | 🟠 Important | ✅ **MAINTENU** |
| **Info** | 30 | ⬇️ -2 (-6%) 🎉 | 🔵 Informatif | Optimisations mineures |
---
@@ -32,25 +32,24 @@
### 🎉 Accomplissement majeur : 100% des warnings éliminés
**Corrections effectuées le 05/10/2025 :**
**Corrections effectuées le 09/11/2025 :**
1.**Suppression de la classe `_RoomTile` non utilisée** (rooms_page_embedded.dart)
2.**Suppression du cast inutile `as int?`** (history_page.dart ligne 201)
3.**Suppression de 4 `.toList()` inutiles dans les spreads** (history_page.dart)
4.**Suppression du champ `_isFirstLoad` non utilisé** (map_page.dart)
5.**Suppression des méthodes `_loadUserSectors` et `_loadUserPassages` non référencées** (map_page.dart)
6.**Suppression de la variable `allSectors` non utilisée** (members_board_passages.dart)
7.**Correction des opérateurs null-aware inutiles** (passage_form_dialog.dart lignes 373, 376)
8.**Re-génération de room.g.dart** avec build_runner pour corriger l'opérateur null-aware
1.**Suppression des `!` inutiles sur savedPassage** (passage_form_dialog.dart lignes 503, 515)
- Création d'une variable locale `confirmedPassage` après la vérification null
- Élimination de 2 warnings `unnecessary_non_null_assertion`
2.**Correction de l'opérateur null-aware dans room.g.dart**
- Remplacement de `?.toList()` par `.toList()` (ligne 29)
- Élimination du warning `invalid_null_aware_operator`
**Impact** :
- 🎯 **-16 warnings** éliminés
- 🚀 Score de qualité du code : **10/10**
-Performance améliorée par suppression de code mort
- 🎯 **-3 warnings** éliminés depuis l'analyse précédente
- 🚀 Score de qualité du code : **10/10** (maintenu)
-Code plus propre et plus sûr
---
## 🔵 Problèmes Informatifs (32 issues) - Réduction massive -84%
## 🔵 Problèmes Informatifs (30 issues) - Réduction continue -6%
### 1. **Interpolation de chaînes** (6 occurrences)
@@ -59,72 +58,67 @@
**Fichiers concernés :**
```
lib/chat/services/chat_service.dart:577
lib/core/services/api_service.dart:344, 784, 810, 882
lib/presentation/dialogs/sector_dialog.dart:577
lib/core/services/api_service.dart:350, 804, 830, 902
lib/presentation/dialogs/sector_dialog.dart:708
```
**🔧 Solution** : Remplacer `"${variable}"` par `"$variable"` quand possible
### 2. **BuildContext async** (5 occurrences)
### 2. **BuildContext async** (7 occurrences)
- `use_build_context_synchronously` : 5 occurrences
- `use_build_context_synchronously` : 7 occurrences
**Fichiers concernés :**
```
lib/presentation/auth/login_page.dart:753
lib/presentation/auth/splash_page.dart:768, 771, 776
lib/presentation/widgets/amicale_form.dart:199
lib/presentation/auth/login_page.dart:733
lib/presentation/auth/register_page.dart:726
lib/presentation/auth/splash_page.dart:760, 763, 768
lib/presentation/widgets/payment_method_selection_dialog.dart:306
lib/presentation/widgets/user_form_dialog.dart:69
```
**🔧 Solution** : Vérifier `mounted` avant d'utiliser `context` dans les callbacks async
### 3. **Optimisations de code** (21 occurrences)
### 3. **Optimisations de code** (17 occurrences)
| Type | Nombre | Solution |
|------|--------|----------|
| `use_super_parameters` | 3 | Utiliser les super parameters (Flutter 3.0+) |
| `depend_on_referenced_packages` | 3 | Ajouter packages au pubspec.yaml |
| `unnecessary_library_name` | 2 | Supprimer directive `library` |
| `unintended_html_in_doc_comment` | 2 | Échapper les `<>` dans les commentaires |
| `sized_box_for_whitespace` | 2 | Utiliser `SizedBox` au lieu de `Container` vide |
| `prefer_interpolation_to_compose_strings` | 2 | Utiliser interpolation au lieu de `+` |
| `deprecated_member_use` | 2 | Remplacer `desiredAccuracy` par `LocationSettings` |
| `use_super_parameters` | 2 | Utiliser les super parameters (Flutter 3.0+) |
| `prefer_final_fields` | 2 | Marquer les champs privés non modifiés comme `final` |
| `unnecessary_to_list_in_spreads` | 1 | Supprimer `.toList()` dans les spreads |
| `sort_child_properties_last` | 1 | Mettre `child` en dernier paramètre |
| `deprecated_member_use` | 1 | Remplacer `isAvailable` par `checkAvailability` |
| `sized_box_for_whitespace` | 2 | Utiliser `SizedBox` au lieu de `Container` vide |
| `unnecessary_library_name` | 1 | Supprimer directive `library` |
| `dangling_library_doc_comments` | 1 | Ajouter `library` ou supprimer le commentaire |
| `curly_braces_in_flow_control_structures` | 1 | Ajouter accolades dans le `if` |
| `sort_child_properties_last` | 1 | Mettre `child` en dernier paramètre |
| `unnecessary_to_list_in_spreads` | 1 | Supprimer `.toList()` dans les spreads |
---
## 🆕 Changements depuis le 29/09/2025
## 🆕 Changements depuis le 05/10/2025
### Améliorations apportées ✅
1. **🎯 Correction complète des warnings** :
- Élimination de 16 warnings (100%)
- Suppression de 186 lignes de code mort
- Nettoyage de 7 fichiers
1. **🎯 Correction complète des nouveaux warnings** :
- Élimination de 3 warnings apparus après les corrections précédentes
- Amélioration de la gestion des types nullable
- Code plus sûr et plus maintenable
2. **🧹 Réduction drastique des infos** :
- De 201 → 32 infos (-84%)
- Élimination des problèmes graves
2. **🧹 Réduction continue des infos** :
- De 32 → 30 infos (-6%)
- Maintien de l'excellence du code
- Conservation uniquement des suggestions mineures
3. **📦 Qualité du code** :
- Score passé de 9.0 → 10/10
- Dette technique réduite de 2.5 → 0.8 jours
- Score maintenu à 10/10
- Dette technique stable à ~0.8 jours
- Maintenabilité excellente
### Fichiers modifiés le 05/10/2025
### Fichiers modifiés le 09/11/2025
```
✅ lib/chat/pages/rooms_page_embedded.dart - Suppression classe _RoomTile
✅ lib/presentation/pages/history_page.dart - Corrections multiples (cast, .toList())
✅ lib/presentation/pages/map_page.dart - Nettoyage code non utilisé
✅ lib/presentation/widgets/members_board_passages.dart - Suppression variable inutile
✅ lib/presentation/widgets/passage_form_dialog.dart - Correction null-aware operators
✅ lib/chat/models/room.g.dart - Re-génération avec build_runner
✅ lib/presentation/widgets/passage_form_dialog.dart - Ajout variable confirmedPassage
✅ lib/chat/models/room.g.dart - Correction opérateur null-aware
```
---
@@ -135,42 +129,42 @@ lib/presentation/widgets/amicale_form.dart:199
| Métrique | 04/09 (baseline) | Aujourd'hui | Évolution |
|----------|------------------|-------------|-----------|
| **Total issues** | 171 | 32 | ⬇️ -139 (-81%) |
| **Total issues** | 171 | 30 | ⬇️ -141 (-82%) |
| **Warnings** | 25 | 0 | ⬇️ -25 (-100%) 🎉 |
| **Infos** | 146 | 32 | ⬇️ -114 (-78%) |
| **Infos** | 146 | 30 | ⬇️ -116 (-79%) |
### Progression par rapport à l'origine (31/08)
| Métrique | 31/08 (origine) | Aujourd'hui | Réduction totale |
|----------|-----------------|-------------|------------------|
| **Total issues** | 551 | 32 | ⬇️ -519 (-94%) 🚀 |
| **Total issues** | 551 | 30 | ⬇️ -521 (-95%) 🚀 |
| **Warnings** | 28 | 0 | ⬇️ -28 (-100%) 🎉 |
| **Infos** | 523 | 32 | ⬇️ -491 (-94%) 🚀 |
| **Infos** | 523 | 30 | ⬇️ -493 (-94%) 🚀 |
---
## 📁 Analyse par Module
### Module Chat (~/lib/chat/)
| Métrique | Valeur | Évolution vs 29/09 |
| Métrique | Valeur | Évolution vs 05/10 |
|----------|--------|---------------------|
| Problèmes totaux | 2 | ⬇️ -66 (-97%) |
| Problèmes totaux | 1 | ⬇️ -1 (-50%) |
| Warnings | 0 | ⬇️ -1 |
| Info | 2 | ⬇️ -65 |
| Info | 1 | Stable |
### Module Core (~/lib/core/)
| Métrique | Valeur | Évolution vs 29/09 |
| Métrique | Valeur | Évolution vs 05/10 |
|----------|--------|---------------------|
| Problèmes totaux | 9 | ⬇️ -5 (-36%) |
| Problèmes totaux | 9 | Stable |
| Warnings | 0 | Stable |
| Info | 9 | ⬇️ -5 |
| Info | 9 | Stable |
### Module Presentation (~/lib/presentation/)
| Métrique | Valeur | Évolution vs 29/09 |
| Métrique | Valeur | Évolution vs 05/10 |
|----------|--------|---------------------|
| Problèmes totaux | 21 | ⬇️ -64 (-75%) |
| Warnings | 0 | ⬇️ -12 |
| Info | 21 | ⬇️ -52 |
| Problèmes totaux | 20 | ⬇️ -1 (-5%) |
| Warnings | 0 | ⬇️ -2 |
| Info | 20 | Stable |
---
@@ -180,10 +174,10 @@ lib/presentation/widgets/amicale_form.dart:199
| Métrique | Valeur actuelle | Objectif | Statut |
|----------|-----------------|----------|------------|
| **Code Health** | 10.0/10 ✨ | 9.0/10 | ✅ **DÉPASSÉ** |
| **Code Health** | 10.0/10 ✨ | 9.0/10 | ✅ **MAINTENU** |
| **Technical Debt** | 0.8 jours | < 2 jours | Excellent |
| **Warnings** | 0 | 0 | **OBJECTIF ATTEINT** |
| **Code Quality** | A+ | A | **DÉPASSÉ** |
| **Warnings** | 0 | 0 | **OBJECTIF MAINTENU** |
| **Code Quality** | A+ | A | **MAINTENU** |
### Historique des analyses
@@ -193,43 +187,39 @@ lib/presentation/widgets/amicale_form.dart:199
| 04/09/2025 | 171 | 0 | 25 | 146 | 3.2.3 | Nettoyage majeur |
| 25/09/2025 | 170 | 0 | 16 | 154 | 3.2.4 | Stable |
| 29/09/2025 | 217 | 0 | 16 | 201 | 3.3.0 | Régression module Chat |
| **05/10/2025** | **32** | **0** | **0** | **32** | **3.3.4** | ** EXCELLENCE ATTEINTE** 🎉 |
| 05/10/2025 | 32 | 0 | 0 | 32 | 3.3.4 | Excellence atteinte 🎉 |
| **09/11/2025** | **30** | **0** | **0** | **30** | **3.3.6** | ** EXCELLENCE MAINTENUE** 🎉 |
### Progression depuis le début (vs origine 31/08)
- **Total** : -519 issues (⬇ **94%**) 🚀
- **Total** : -521 issues (⬇ **95%**) 🚀
- **Warnings** : -28 issues (⬇ **100%**) 🎉
- **Infos** : -491 issues (⬇ **94%**) 🚀
- **Infos** : -493 issues (⬇ **94%**) 🚀
---
## 🎯 Accomplissements de cette session
### ✅ Travail effectué aujourd'hui (05/10/2025)
### ✅ Travail effectué aujourd'hui (09/11/2025)
1. **🎯 Élimination complète des warnings (16 0)**
- Correction de 8 warnings distincts
- Nettoyage de 7 fichiers
- 100% des warnings éliminés
1. **🎯 Maintien de l'excellence (0 warning, 0 error)**
- Correction des 3 nouveaux warnings détectés
- Amélioration de la gestion des types nullable
- Code plus sûr et maintenable
2. **🧹 Nettoyage massif du code**
- Suppression de 186 lignes de code mort
- Élimination des classes/méthodes/variables non utilisées
- Simplification de la logique dans plusieurs fichiers
2. **🧹 Optimisation du code**
- Suppression des `!` inutiles dans passage_form_dialog.dart
- Création de variable locale `confirmedPassage` pour type safety
- Correction de l'opérateur null-aware dans room.g.dart
3. ** Optimisation des performances**
- Suppression des `.toList()` redondants
- Correction des opérateurs null-aware inutiles
- Nettoyage des casts superflus
3. ** Performance et qualité**
- Build runner exécuté avec succès (28 fichiers générés)
- Vérification complète avec flutter analyze
- Réduction de 2 infos supplémentaires (-6%)
4. **📦 Re-génération des fichiers Hive**
- Build runner exécuté avec succès
- Correction automatique du fichier room.g.dart
- 30 fichiers générés/mis à jour
5. **📊 Amélioration drastique de la qualité**
- Score de code health : 9.0 10.0/10
- Dette technique : 2.5 0.8 jours
- Réduction de 85% des issues totales
4. **📊 Maintien du score parfait**
- Score de code health : 10.0/10 (maintenu)
- Dette technique : 0.8 jours (stable)
- 95% de réduction totale depuis l'origine
---
@@ -238,23 +228,22 @@ lib/presentation/widgets/amicale_form.dart:199
### Phase 1 : Optimisations mineures restantes (0.5 jour) - Optionnel
- [ ] Corriger 6 interpolations de chaînes (unnecessary_brace_in_string_interps)
- [ ] Améliorer 5 BuildContext async (use_build_context_synchronously)
- [ ] Appliquer 3 super parameters (use_super_parameters)
- [ ] Améliorer 7 BuildContext async (use_build_context_synchronously)
- [ ] Ajouter 3 packages au pubspec (depend_on_referenced_packages)
- [ ] Remplacer 2 desiredAccuracy deprecated (deprecated_member_use)
### Phase 2 : Perfectionnement (0.5 jour) - Optionnel
### Phase 2 : Perfectionnement (0.3 jour) - Optionnel
- [ ] Nettoyer 2 library names inutiles
- [ ] Corriger 2 commentaires HTML mal formatés
- [ ] Remplacer 2 Container par SizedBox
- [ ] Améliorer 2 concaténations de chaînes
- [ ] Appliquer 2 super parameters (use_super_parameters)
- [ ] Marquer 2 champs comme final (prefer_final_fields)
- [ ] Remplacer 2 Container par SizedBox (sized_box_for_whitespace)
- [ ] Supprimer 1 library name inutile
### Phase 3 : Polish final (0.2 jour) - Optionnel
### Phase 3 : Polish final (0.1 jour) - Optionnel
- [ ] Marquer 2 champs comme final
- [ ] Corriger 1 deprecated member
- [ ] Ajouter accolades dans 1 if
- [ ] Corriger 1 dangling library doc comment
- [ ] Déplacer 1 paramètre child en dernier
- [ ] Supprimer 1 .toList() dans spread
**💡 Note** : Ces optimisations sont toutes de niveau "info" (suggestions de style). Elles n'affectent ni la stabilité ni les performances de l'application.
@@ -266,16 +255,17 @@ lib/presentation/widgets/amicale_form.dart:199
- [x] Code compile sans erreur
- [x] **Tous les warnings corrigés (0/0)** 🎉
- [x] Réduction majeure des issues (-94% depuis origine)
- [x] Réduction majeure des issues (-95% depuis origine)
- [x] Technical debt < 1 jour (0.8 jours)
- [x] Score de maintenabilité 10/10
- [x] Navigation par sous-routes implémentée
- [x] Code mort éliminé
- [x] Optimisations de performance appliquées
- [x] Gestion sûre des types nullable
### En cours (optionnel)
- [ ] Suggestions de style (32 infos restantes)
- [ ] Suggestions de style (30 infos restantes)
- [ ] Tests unitaires (0% objectif 80%)
### À faire (long terme)
@@ -288,36 +278,37 @@ lib/presentation/widgets/amicale_form.dart:199
## 🔄 Prochaines Étapes
1. ** Terminé** : Éliminer tous les warnings **FAIT LE 05/10** 🎉
2. **Optionnel** : Appliquer les 32 suggestions de style (infos)
3. **Version 3.4.0** : Implémentation Stripe Tap to Pay complète
4. **Version 4.0.0** : Tests unitaires + CI/CD
1. ** Terminé** : Éliminer tous les warnings **MAINTENU** 🎉
2. ** Terminé** : Corriger les warnings apparus après corrections **FAIT LE 09/11** 🎉
3. **Optionnel** : Appliquer les 30 suggestions de style (infos)
4. **Version 3.4.0** : Implémentation Stripe Tap to Pay complète
5. **Version 4.0.0** : Tests unitaires + CI/CD
---
## 📊 Métriques Clés
- **Réduction depuis le 29/09** : -185 issues (-85%) 🚀
- **Réduction totale depuis origine** : -519 issues (-94%) 🚀
- **Code Health** : 10.0/10 ( +1.0 point)
- **Technical Debt** : 0.8 jours ( -1.7 jours)
- **Temps de correction estimé restant** : 1.2 jours (uniquement optimisations de style)
- **Réduction depuis le 05/10** : -2 issues (-6%)
- **Réduction totale depuis origine** : -521 issues (-95%) 🚀
- **Code Health** : 10.0/10 (maintenu)
- **Technical Debt** : 0.8 jours (stable)
- **Temps de correction estimé restant** : 0.9 jours (uniquement optimisations de style)
---
## 🏆 Points Positifs Majeurs
1. **🎉 EXCELLENCE ATTEINTE** : 0 warning, 0 error !
2. **🚀 Réduction massive** : -94% des issues depuis l'origine
3. ** Score parfait** : Code Health 10/10
1. **🎉 EXCELLENCE MAINTENUE** : 0 warning, 0 error !
2. **🚀 Réduction massive** : -95% des issues depuis l'origine
3. ** Score parfait maintenu** : Code Health 10/10
4. ** Performance optimale** : Dette technique minimal (0.8j)
5. **📦 Build stable** : Version 3.3.4 prête pour production
6. **🧹 Code propre** : Suppression de 186 lignes de code mort
7. **🎯 Objectifs dépassés** : Tous les warnings éliminés (objectif 100% atteint)
5. **📦 Build stable** : Version 3.3.6 prête pour production
6. **🧹 Code propre** : Gestion sûre des types nullable
7. **🎯 Objectifs maintenus** : Excellence préservée dans le temps
## ✅ Points d'Attention (mineurs)
1. **32 suggestions de style** : Purement cosmétiques, sans impact fonctionnel
1. **30 suggestions de style** : Purement cosmétiques, sans impact fonctionnel
2. **Tests unitaires** : À implémenter (optionnel pour cette phase)
3. **Documentation** : À compléter (long terme)
@@ -327,13 +318,19 @@ lib/presentation/widgets/amicale_form.dart:199
**État actuel : EXCELLENT**
L'application GEOSECTOR a atteint un niveau de qualité exceptionnel avec :
- **0 error, 0 warning** (objectif principal atteint)
- 🚀 **Réduction de 94% des issues** depuis l'origine
- **Score parfait 10/10** pour le code health
- **Dette technique minimale** (0.8 jours)
L'application GEOSECTOR maintient un niveau de qualité exceptionnel avec :
- **0 error, 0 warning** (objectif principal maintenu)
- 🚀 **Réduction de 95% des issues** depuis l'origine
- **Score parfait 10/10** pour le code health (maintenu)
- **Dette technique minimale** (0.8 jours, stable)
Les 32 infos restantes sont uniquement des **suggestions de style** sans impact sur la stabilité ou les performances. L'application est prête pour la production avec une qualité de code exceptionnelle.
Les 30 infos restantes sont uniquement des **suggestions de style** sans impact sur la stabilité ou les performances. L'application est prête pour la production avec une qualité de code exceptionnelle et maintenue dans le temps.
**Nouvelles corrections du 09/11/2025** :
- Amélioration de la gestion des types nullable
- Élimination des `!` inutiles grâce à des variables locales typées
- Correction des opérateurs null-aware dans les fichiers générés
- Maintien de l'excellence avec -2 infos supplémentaires
---

View File

@@ -964,7 +964,7 @@ GEOSECTOR utilise des **clés Stripe différentes** selon l'environnement pour s
|---------------|-----|-------------------|----------------|------|-------|
| **DEV** | `dapp.geosector.fr` | Test Pierre | `pk_test_51QwoVN...`<br>`sk_test_51QwoVN...` | `test` | Développement |
| **REC** | `rapp.geosector.fr` | Test Client | `CLIENT_PK_TEST_A_REMPLACER`<br>`CLIENT_SK_TEST_A_REMPLACER` | `test` | Recette |
| **PROD** | `app.geosector.fr` | Live Client | `CLIENT_PK_LIVE_A_REMPLACER`<br>`CLIENT_SK_LIVE_A_REMPLACER` | `live` | Production |
| **PROD** | `app3.geosector.fr` | Live Client | `CLIENT_PK_LIVE_A_REMPLACER`<br>`CLIENT_SK_LIVE_A_REMPLACER` | `live` | Production |
#### **Types de clés Stripe**
@@ -1014,7 +1014,7 @@ Pour configurer REC et PROD, le client doit fournir ses clés depuis son **Dashb
'mode' => 'test',
],
// Configuration PRODUCTION (app.geosector.fr)
// Configuration PRODUCTION (app3.geosector.fr)
'stripe' => [
'public_key_live' => 'CLIENT_PK_LIVE_A_REMPLACER', // À remplacer
'secret_key_live' => 'CLIENT_SK_LIVE_A_REMPLACER', // À remplacer

View File

@@ -0,0 +1,464 @@
# 🔧 Migration Backend Stripe - Option A (Tout en 1)
## 📋 Objectif
Optimiser la création de compte Stripe Connect en **1 seule requête** côté Flutter qui crée :
1. Le compte Stripe Connect
2. La Location Terminal
3. Le lien d'onboarding
---
## 🗄️ 1. Modification de la base de données
### **Ajouter la colonne `stripe_location_id`**
```sql
ALTER TABLE amicales
ADD COLUMN stripe_location_id VARCHAR(255) NULL
AFTER stripe_id;
```
**Vérification** :
```sql
DESCRIBE amicales;
```
Doit afficher :
```
+-------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+--------------+------+-----+---------+-------+
| stripe_id | varchar(255) | YES | | NULL | |
| stripe_location_id| varchar(255) | YES | | NULL | |
+-------------------+--------------+------+-----+---------+-------+
```
---
## 🔧 2. Modification de l'endpoint `POST /stripe/accounts`
### **Fichier** : `app/Http/Controllers/StripeController.php` (ou similaire)
### **Méthode** : `createAccount()` ou `store()`
### **Code proposé** :
```php
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Models\Amicale;
/**
* Créer un compte Stripe Connect avec Location Terminal et lien d'onboarding
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function createStripeAccount(Request $request)
{
$request->validate([
'fk_entite' => 'required|integer|exists:amicales,id',
'return_url' => 'required|string|url',
'refresh_url' => 'required|string|url',
]);
$fkEntite = $request->fk_entite;
$amicale = Amicale::findOrFail($fkEntite);
// Vérifier si un compte existe déjà
if (!empty($amicale->stripe_id)) {
return $this->handleExistingAccount($amicale, $request);
}
DB::beginTransaction();
try {
// Configurer la clé Stripe (selon environnement)
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
// 1⃣ Créer le compte Stripe Connect Express
$account = \Stripe\Account::create([
'type' => 'express',
'country' => 'FR',
'email' => $amicale->email,
'business_type' => 'non_profit', // ou 'company' selon le cas
'business_profile' => [
'name' => $amicale->name,
'url' => config('app.url'),
],
'capabilities' => [
'card_payments' => ['requested' => true],
'transfers' => ['requested' => true],
],
]);
\Log::info('Stripe account created', [
'amicale_id' => $amicale->id,
'account_id' => $account->id,
]);
// 2⃣ Créer la Location Terminal pour Tap to Pay
$location = \Stripe\Terminal\Location::create([
'display_name' => $amicale->name,
'address' => [
'line1' => $amicale->adresse1 ?: 'Non renseigné',
'line2' => $amicale->adresse2,
'city' => $amicale->ville ?: 'Non renseigné',
'postal_code' => $amicale->code_postal ?: '00000',
'country' => 'FR',
],
], [
'stripe_account' => $account->id, // ← Important : Connect account
]);
\Log::info('Stripe Terminal Location created', [
'amicale_id' => $amicale->id,
'location_id' => $location->id,
]);
// 3⃣ Créer le lien d'onboarding
$accountLink = \Stripe\AccountLink::create([
'account' => $account->id,
'refresh_url' => $request->refresh_url,
'return_url' => $request->return_url,
'type' => 'account_onboarding',
]);
\Log::info('Stripe onboarding link created', [
'amicale_id' => $amicale->id,
'account_id' => $account->id,
]);
// 4⃣ Sauvegarder TOUT en base de données
$amicale->stripe_id = $account->id;
$amicale->stripe_location_id = $location->id;
$amicale->chk_stripe = true;
$amicale->save();
DB::commit();
// 5⃣ Retourner TOUTES les informations
return response()->json([
'success' => true,
'account_id' => $account->id,
'location_id' => $location->id,
'onboarding_url' => $accountLink->url,
'charges_enabled' => $account->charges_enabled,
'payouts_enabled' => $account->payouts_enabled,
'existing' => false,
'message' => 'Compte Stripe Connect créé avec succès',
], 201);
} catch (\Stripe\Exception\ApiErrorException $e) {
DB::rollBack();
\Log::error('Stripe API error', [
'amicale_id' => $amicale->id,
'error' => $e->getMessage(),
'type' => get_class($e),
]);
return response()->json([
'success' => false,
'message' => 'Erreur Stripe : ' . $e->getMessage(),
], 500);
} catch (\Exception $e) {
DB::rollBack();
\Log::error('Stripe account creation failed', [
'amicale_id' => $amicale->id,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return response()->json([
'success' => false,
'message' => 'Erreur lors de la création du compte Stripe',
], 500);
}
}
/**
* Gérer le cas d'un compte Stripe existant
*/
private function handleExistingAccount(Amicale $amicale, Request $request)
{
try {
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
// Récupérer les infos du compte existant
$account = \Stripe\Account::retrieve($amicale->stripe_id);
// Si pas de location_id, la créer maintenant
if (empty($amicale->stripe_location_id)) {
$location = \Stripe\Terminal\Location::create([
'display_name' => $amicale->name,
'address' => [
'line1' => $amicale->adresse1 ?: 'Non renseigné',
'city' => $amicale->ville ?: 'Non renseigné',
'postal_code' => $amicale->code_postal ?: '00000',
'country' => 'FR',
],
], [
'stripe_account' => $amicale->stripe_id,
]);
$amicale->stripe_location_id = $location->id;
$amicale->save();
\Log::info('Location created for existing account', [
'amicale_id' => $amicale->id,
'location_id' => $location->id,
]);
}
// Si le compte est déjà complètement configuré
if ($account->charges_enabled && $account->payouts_enabled) {
return response()->json([
'success' => true,
'account_id' => $amicale->stripe_id,
'location_id' => $amicale->stripe_location_id,
'onboarding_url' => null,
'charges_enabled' => true,
'payouts_enabled' => true,
'existing' => true,
'message' => 'Compte Stripe déjà configuré et actif',
]);
}
// Compte existant mais configuration incomplète : générer un nouveau lien
$accountLink = \Stripe\AccountLink::create([
'account' => $amicale->stripe_id,
'refresh_url' => $request->refresh_url,
'return_url' => $request->return_url,
'type' => 'account_onboarding',
]);
return response()->json([
'success' => true,
'account_id' => $amicale->stripe_id,
'location_id' => $amicale->stripe_location_id,
'onboarding_url' => $accountLink->url,
'charges_enabled' => $account->charges_enabled,
'payouts_enabled' => $account->payouts_enabled,
'existing' => true,
'message' => 'Compte existant, configuration à finaliser',
]);
} catch (\Exception $e) {
\Log::error('Error handling existing account', [
'amicale_id' => $amicale->id,
'error' => $e->getMessage(),
]);
return response()->json([
'success' => false,
'message' => 'Erreur lors de la vérification du compte existant',
], 500);
}
}
```
---
## 📡 3. Modification de l'endpoint `GET /stripe/accounts/{id}/status`
Ajouter `location_id` dans la réponse :
```php
public function checkAccountStatus($amicaleId)
{
$amicale = Amicale::findOrFail($amicaleId);
if (empty($amicale->stripe_id)) {
return response()->json([
'has_account' => false,
'account_id' => null,
'location_id' => null,
'charges_enabled' => false,
'payouts_enabled' => false,
'onboarding_completed' => false,
]);
}
try {
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
$account = \Stripe\Account::retrieve($amicale->stripe_id);
return response()->json([
'has_account' => true,
'account_id' => $amicale->stripe_id,
'location_id' => $amicale->stripe_location_id, // ← Ajouté
'charges_enabled' => $account->charges_enabled,
'payouts_enabled' => $account->payouts_enabled,
'onboarding_completed' => $account->details_submitted,
]);
} catch (\Exception $e) {
return response()->json([
'has_account' => false,
'error' => $e->getMessage(),
], 500);
}
}
```
---
## 🗑️ 4. Endpoint à SUPPRIMER (devenu inutile)
### **❌ `POST /stripe/locations`**
Cet endpoint n'est plus nécessaire car la Location est créée automatiquement dans `POST /stripe/accounts`.
**Option 1** : Supprimer complètement
**Option 2** : Le garder pour compatibilité temporaire (si utilisé ailleurs)
---
## 📝 5. Modification du modèle Eloquent
### **Fichier** : `app/Models/Amicale.php`
Ajouter le champ `stripe_location_id` :
```php
protected $fillable = [
// ... autres champs
'stripe_id',
'stripe_location_id', // ← Ajouté
'chk_stripe',
];
protected $casts = [
'chk_stripe' => 'boolean',
];
```
---
## ✅ 6. Tests à effectuer
### **Test 1 : Nouvelle amicale**
```bash
curl -X POST http://localhost/api/stripe/accounts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {token}" \
-d '{
"fk_entite": 123,
"return_url": "https://app.geosector.fr/stripe/success",
"refresh_url": "https://app.geosector.fr/stripe/refresh"
}'
```
**Réponse attendue** :
```json
{
"success": true,
"account_id": "acct_xxxxxxxxxxxxx",
"location_id": "tml_xxxxxxxxxxxxx",
"onboarding_url": "https://connect.stripe.com/setup/...",
"charges_enabled": false,
"payouts_enabled": false,
"existing": false,
"message": "Compte Stripe Connect créé avec succès"
}
```
### **Test 2 : Amicale avec compte existant**
```bash
curl -X POST http://localhost/api/stripe/accounts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {token}" \
-d '{
"fk_entite": 456,
"return_url": "https://app.geosector.fr/stripe/success",
"refresh_url": "https://app.geosector.fr/stripe/refresh"
}'
```
**Réponse attendue** :
```json
{
"success": true,
"account_id": "acct_xxxxxxxxxxxxx",
"location_id": "tml_xxxxxxxxxxxxx",
"onboarding_url": null,
"charges_enabled": true,
"payouts_enabled": true,
"existing": true,
"message": "Compte Stripe déjà configuré et actif"
}
```
### **Test 3 : Vérifier la BDD**
```sql
SELECT id, name, stripe_id, stripe_location_id, chk_stripe
FROM amicales
WHERE id = 123;
```
**Résultat attendu** :
```
+-----+------------------+-------------------+-------------------+------------+
| id | name | stripe_id | stripe_location_id| chk_stripe |
+-----+------------------+-------------------+-------------------+------------+
| 123 | Pompiers Paris15 | acct_xxxxxxxxxxxxx| tml_xxxxxxxxxxxxx | 1 |
+-----+------------------+-------------------+-------------------+------------+
```
---
## 🚀 7. Déploiement
### **Étapes** :
1. ✅ Appliquer la migration SQL
2. ✅ Déployer le code Backend modifié
3. ✅ Tester avec Postman/curl
4. ✅ Déployer le code Flutter modifié
5. ✅ Tester le flow complet depuis l'app
---
## 📊 Comparaison Avant/Après
| Aspect | Avant | Après |
|--------|-------|-------|
| **Appels API Flutter → Backend** | 3 | 1 |
| **Appels Backend → Stripe** | 3 | 3 (mais atomiques) |
| **Latence totale** | ~3-5s | ~1-2s |
| **Gestion erreurs** | Complexe | Simplifié avec transaction |
| **Atomicité** | ❌ Non | ✅ Oui (DB transaction) |
| **Location ID sauvegardé** | ❌ Non | ✅ Oui |
---
## 🎯 Bénéfices
1.**Performance** : Latence divisée par 2-3
2.**Fiabilité** : Transaction BDD garantit la cohérence
3.**Simplicité** : Code Flutter plus simple
4.**Maintenance** : Moins de code à maintenir
5.**Traçabilité** : Logs centralisés côté Backend
6.**Tap to Pay prêt** : `location_id` disponible immédiatement
---
## ⚠️ Points d'attention
1. **Rollback** : Si la transaction échoue, rien n'est sauvegardé (bon comportement)
2. **Logs** : Bien logger chaque étape pour le debug
3. **Stripe Connect limitations** : Respecter les rate limits Stripe
4. **Tests** : Tester avec des comptes Stripe de test d'abord
---
## 📚 Ressources
- [Stripe Connect Express Accounts](https://stripe.com/docs/connect/express-accounts)
- [Stripe Terminal Locations](https://stripe.com/docs/terminal/fleet/locations)
- [Stripe Account Links](https://stripe.com/docs/connect/account-links)

View File

@@ -0,0 +1,212 @@
# ✅ Migration Stripe "Option A" - Résumé
## 📅 Date : 3 novembre 2025
---
## 🎯 Objectif
Optimiser la création de compte Stripe Connect en **1 seule requête** côté Flutter qui crée :
1. Le compte Stripe Connect
2. La Location Terminal (pour Tap to Pay)
3. Le lien d'onboarding
---
## ✅ Modifications Flutter APPLIQUÉES
### **1. Modèle `AmicaleModel`**
- ✅ Ajout du champ `stripeLocationId` (HiveField 27)
- ✅ Ajout dans le constructeur
- ✅ Ajout dans `fromJson()`
- ✅ Ajout dans `toJson()`
- ✅ Ajout dans `copyWith()`
- ✅ Adaptateurs Hive régénérés
### **2. Service `StripeTapToPayService`**
- ✅ Récupération du `location_id` depuis `amicale.stripeLocationId`
- ✅ Validation que le `location_id` existe avant initialisation
- ✅ Suppression de la méthode `_fetchConfiguration()` (inutile)
- ✅ Plus besoin de l'endpoint `GET /api/stripe/configuration`
### **3. Service `StripeConnectService`**
- ✅ Simplification de `createStripeAccount()` : 1 seule requête
- ✅ Ajout des URLs `return_url` et `refresh_url` dans la requête
- ✅ Récupération de `location_id` et `onboarding_url` dans la réponse
- ✅ Suppression de l'appel à `/stripe/locations`
- ✅ Ajout de `locationId` dans `StripeAccountStatus`
- ✅ Conservation de `getOnboardingLink()` (marqué déprécié)
---
## ⚠️ Modifications Backend REQUISES
### **1. Base de données**
```sql
ALTER TABLE amicales
ADD COLUMN stripe_location_id VARCHAR(255) NULL
AFTER stripe_id;
```
### **2. Endpoint `POST /stripe/accounts`**
**Modifications nécessaires** :
- Accepter `return_url` et `refresh_url` dans la requête
- Créer le compte Stripe Connect
- Créer la Location Terminal
- Créer le lien d'onboarding
- Sauvegarder `stripe_id`, `stripe_location_id`, `chk_stripe` en BDD
- Retourner : `account_id`, `location_id`, `onboarding_url`, `charges_enabled`, `payouts_enabled`, `existing`
**Voir le code complet** : `docs/STRIPE-BACKEND-MIGRATION.md`
### **3. Endpoint `GET /stripe/accounts/{id}/status`**
**Modification** :
- Ajouter `location_id` dans la réponse JSON
### **4. Endpoint `POST /stripe/locations`**
**Action** : À supprimer (devenu inutile) ou garder pour compatibilité temporaire
---
## 📊 Comparaison Avant/Après
| Aspect | Avant | Après |
|--------|-------|-------|
| **Appels API Flutter → Backend** | 3 | 1 |
| **Latence totale** | ~3-5s | ~1-2s |
| **Gestion erreurs** | Complexe (try/catch multiples) | Simplifié (transaction atomique) |
| **Atomicité BDD** | ❌ Non garantie | ✅ Oui (transaction) |
| **Location ID sauvegardé** | ❌ Non | ✅ Oui |
| **Code à maintenir** | Plus complexe | Plus simple |
---
## 🔄 Flow optimisé
```
Flutter (1 appel) Backend (3 appels Stripe) Stripe API
│ │ │
│ POST /stripe/accounts │ │
│ { │ │
│ fk_entite: 123, │ │
│ return_url: "...", │ │
│ refresh_url: "..." │ │
│ } │ │
├─────────────────────────────>│ │
│ │ 1. Create Account │
│ ├──────────────────────────>│
│ │<──────────────────────────┤
│ │ account_id: acct_xxx │
│ │ │
│ │ 2. Create Location │
│ ├──────────────────────────>│
│ │<──────────────────────────┤
│ │ location_id: tml_xxx │
│ │ │
│ │ 3. Create AccountLink │
│ ├──────────────────────────>│
│ │<──────────────────────────┤
│ │ onboarding_url │
│ │ │
│ │ (Sauvegarde en BDD) │
│ │ │
│<─────────────────────────────┤ │
│ { │ │
│ account_id: acct_xxx, │ │
│ location_id: tml_xxx, │ │
│ onboarding_url: "..." │ │
│ } │ │
```
---
## ✅ Tests à effectuer
### **Backend (après implémentation)**
1. [ ] Migration SQL appliquée
2. [ ] Test avec Postman : nouvelle amicale
3. [ ] Test avec Postman : amicale existante
4. [ ] Vérification BDD : `stripe_location_id` bien sauvegardé
5. [ ] Logs vérifiés (pas d'erreurs)
### **Flutter (maintenant)**
1. [ ] Compilation OK (déjà fait ✅)
2. [ ] Test création compte depuis l'app Web admin
3. [ ] Vérification que le `location_id` est bien dans l'amicale
4. [ ] Test paiement Tap to Pay avec le `location_id`
5. [ ] Vérification que l'erreur "Erreur inattendue" n'apparaît plus
---
## 🚨 Points d'attention
### **Backend**
- Utiliser une **transaction BDD** pour garantir l'atomicité
- Bien logger chaque étape pour le debug
- Gérer le cas des comptes existants (avec/sans `location_id`)
- Tester avec des clés Stripe de test d'abord
### **Flutter**
- Le backend doit être déployé **AVANT** de tester l'app
- Si le backend n'est pas prêt, l'app retournera une erreur 400/500
- Les anciens comptes sans `location_id` devront être migrés
---
## 📚 Documentation
- **Code Backend complet** : `docs/STRIPE-BACKEND-MIGRATION.md`
- **Code Flutter modifié** : `lib/core/services/stripe_connect_service.dart`
- **Modèle modifié** : `lib/core/data/models/amicale_model.dart`
- **Service Tap to Pay** : `lib/core/services/stripe_tap_to_pay_service.dart`
---
## 🎯 Prochaines étapes
### **Étape 1 : Backend**
1. Implémenter les modifications backend
2. Tester avec Postman
3. Valider que tout fonctionne
### **Étape 2 : Migration des données existantes**
Pour les amicales qui ont déjà un `stripe_id` mais pas de `stripe_location_id` :
```sql
-- Identifier les amicales concernées
SELECT id, name, stripe_id, stripe_location_id
FROM amicales
WHERE stripe_id IS NOT NULL
AND stripe_location_id IS NULL;
```
**Option A** : Les créer manuellement via l'API Stripe
**Option B** : Ajouter un endpoint de migration `POST /stripe/migrate-locations`
### **Étape 3 : Tests complets**
1. Test création nouvelle amicale
2. Test amicale existante avec compte
3. Test Tap to Pay avec paiement CB
4. Validation que l'erreur est corrigée
---
## ✅ État actuel
| Composant | État | Commentaire |
|-----------|------|-------------|
| **AmicaleModel** | ✅ Modifié | Champ `stripeLocationId` ajouté |
| **StripeTapToPayService** | ✅ Modifié | Utilise `amicale.stripeLocationId` |
| **StripeConnectService** | ✅ Modifié | 1 seule requête optimisée |
| **Build Runner** | ✅ Exécuté | Adaptateurs Hive régénérés |
| **Compilation Flutter** | ✅ OK | Aucune erreur |
| **Backend** | ⏳ En attente | À implémenter |
| **Tests** | ⏳ En attente | Après implémentation Backend |
---
## 📞 Support
- Documentation Stripe Connect : https://stripe.com/docs/connect
- Documentation Terminal Locations : https://stripe.com/docs/terminal/fleet/locations
- Documentation AccountLinks : https://stripe.com/docs/connect/account-links

File diff suppressed because it is too large Load Diff

View File

@@ -1,444 +0,0 @@
# GEOSECTOR v3.2.4
## Points à traiter
---
**Client** : GEOSECTOR
**Date** : 11 septembre 2025
**Deadline** : 08 octobre 2025 (Congrès)
**Version actuelle** : v3.2.4
**Version cible** : v3.4.4
---
<div style="page-break-after: always;"></div>
## SOMMAIRE
1. [Priorité 1 - Corrections critiques](#priorité-1---corrections-critiques)
2. [Priorité 2 - Améliorations fonctionnelles](#priorité-2---améliorations-fonctionnelles)
3. [Priorité 3 - Interface utilisateur](#priorité-3---interface-utilisateur)
4. [Restrictions d'accès](#restrictions-daccès)
5. [Mode Super Admin](#mode-super-admin)
6. [Processus d'inscription](#processus-dinscription)
7. [Module Stripe](#module-stripe)
8. [Planning prévisionnel](#planning-prévisionnel)
9. [Point financier](#point-financier)
---
<div style="page-break-after: always;"></div>
## PRIORITÉ 1 - Corrections critiques
### 🔐 Authentification et sécurité
**1. Problème de déconnexion intempestive**
- [x] **Symptôme** : Le rafraîchissement de la page (F5) déconnecte l'utilisateur (05/10/2025)
- [x] **Impact** : Perte de session et du travail en cours
- [x] **Correction** : Maintenir la session active lors du rafraîchissement via endpoint GET /api/user/session
**2. Gestion des mots de passe**
- [x] **Symptôme** : Le mot de passe généré automatiquement contient des espaces
- [x] **Impact** : Impossibilité de connexion avec le mot de passe fourni
- [x] **Correction** : Générer des mots de passe sans espaces
### 📝 Formulaires et saisie de données
**3. Saisie des passages**
- [x] **Symptôme** : Le champ "nom" est obligatoire lors de la saisie d'un passage
- [x] **Impact** : Blocage si le nom n'est pas connu
- [x] **Correction** : Rendre le champ nom optionnel
**4. Modification des secteurs**
- [x] **Symptôme** : Le changement de membre affecté à un secteur n'est pas sauvegardé
- [x] **Impact** : Incohérence dans l'attribution des secteurs
- [x] **Correction** : Corriger la sauvegarde de l'affectation
**5. Enregistrement des passages**
- [ ] **Symptôme** : L'enregistrement d'un nouveau passage ne fonctionne pas correctement
- [ ] **Impact** : Impossibilité d'enregistrer de nouveaux passages
- [ ] **Correction** : Vérifier et corriger le processus d'enregistrement
---
## PRIORITÉ 2 - Améliorations fonctionnelles
### 👥 Gestion des membres
**Liste des membres avec statistiques**
- [x] Afficher la liste des membres avec leurs statistiques (comme ancienne version)
- [x] Vue d'ensemble rapide des performances de chaque membre
**Filtres et organisation**
- [ ] Ajouter des filtres sur la liste des membres dans "Amicale et membres"
- [ ] Afficher les membres sélectionnés en haut de liste lors de modifications
**Gestion des identifiants**
- [ ] Permettre la modification de l'identifiant utilisateur
- [ ] Email non obligatoire si identifiant et mot de passe sont saisis manuellement
### 📊 Historique et reporting
**Sélection avancée**
- [x] Permettre le choix du membre dans l'historique
- [x] Ajouter des sélecteurs de dates (début/fin) dans l'historique
**Affichage et visibilité**
- [x] Corriger le problème de logo blanc sur blanc pour les passages "à finaliser" (04/10/2025)
- [ ] Historique en bas : 1-2 adresses seulement visibles, impossibilité de cliquer dessus
- [x] Ajouter une ligne avec les totaux dans l'historique
### 🗺️ Carte et géolocalisation
**Configuration de la carte**
- [x] Simplifier le système de zoom : zoom par défaut à 15, conservation du zoom utilisateur uniquement (05/10/2025)
- [x] Conservation du zoom lors de la sélection d'un secteur dans la combobox - Le zoom reste inchangé au lieu de s'ajuster automatiquement (05/10/2025)
- [x] Centrage GPS amicale au premier chargement - La carte se centre sur les coordonnées GPS de l'amicale au lieu des secteurs (05/10/2025)
- [x] Suppression du filtrage côté client - Élimination du double filtrage inutile des secteurs et passages (l'API filtre déjà selon le rôle) (05/10/2025)
- [x] Corriger l'affichage des passages par défaut en mode admin (filtre "Aucun passage" non respecté) (04/10/2025)
- [x] Stabiliser les labels de secteurs (nombre de passages/membres) lors de la sélection d'un secteur (04/10/2025)
- [ ] Définir un zoom maximal pour éviter le sur-zoom
- [ ] Étudier l'utilisation d'un style de carte type Snapchat
**Mode terrain**
- [ ] Optimiser la précision et la fiabilité du GPS
- [ ] Améliorer la géolocalisation en mode terrain
- [ ] Mode Web utilisateur : impossible de se déplacer sur la carte en mode terrain (retour automatique à la position)
**Divers**
**Synchronisation des données**
- [x] Membre rattaché à un secteur avec 15 passages visibles sur la carte mais affiche 0 passage à finaliser en mode utilisateur - Correction du filtrage des passages de type 2 (À finaliser) pour afficher tous les passages de ce type en mode utilisateur (05/10/2025)
**Performance et formulaires**
- [ ] Bloquer l'enregistrement à 1 seul lors de la création de membre (actuellement très long, plusieurs clics créent X membres en double)
- [x] Simplifier le script de déploiement (suppression du choix Fast/Release) (04/10/2025)
- [x] Optimiser le rechargement de la carte : secteurs chargés uniquement lors de création/modification, pas en temps réel (04/10/2025)
- [x] Nettoyage du code : réduction des warnings Flutter de 16 à 6 (-62.5%) via suppression des imports non utilisés (04/10/2025)
**Carte et navigation**
- [ ] Mode terrain smartphone : carte trop petite, le zoom revient automatiquement et empêche de dézoomer pour voir les points d'intérêt
- [ ] Points de carte affichés devant les textes (en admin et en utilisateur)
- [ ] Listing des rues invisible (le clavier se met devant)
- [ ] Recherche de rue : ne trouve pas si pas à proximité même si la rue est dans le secteur
- [x] Revoir la couleur des pointeurs sur la carte (04/10/2025)
- [x] Ajouter un filtre de type de passage sur la carte admin (04/10/2025)
- [x] Mode terrain : rayon d'action réduit à 500m pour affichage des passages (04/10/2025)
- [x] Mode terrain : afficher tous les types de passages (pas seulement "à finaliser") (04/10/2025)
- [x] Mode terrain : marqueurs carte avec couleurs selon type de passage (04/10/2025)
**Fonctionnalités utilisateur**
- [ ] Carte en mode utilisateur : actuellement consultable uniquement, affiche l'adresse au clic - évaluer la possibilité de valider un passage directement depuis la carte
- [ ] Désactiver temporairement l'envoi de reçu (ne doit pas encore être actif)
### 📋 Gestion des passages
**Interface et interaction**
- [x] Clic sur la card d'un passage dans list_widget pour le modifier directement (04/10/2025)
- [x] Mémoriser la dernière adresse saisie dans le formulaire de passage pour l'afficher à la prochaine création (04/10/2025)
**Actions groupées**
- [ ] Permettre la suppression de plusieurs passages en une seule fois
- [ ] Implémenter la possibilité de récupérer des passages supprimés (corbeille/historique)
**Statistiques et graphiques**
- [ ] Corriger l'affichage du règlement par chèque qui n'apparaît pas dans le graphe pie
- [x] Corriger l'affichage du graphique Pie qui affichait 100% effectués (filtre excluait les passages "à finaliser") (04/10/2025)
- [x] Corriger le bug de calcul du total des paiements dans l'historique (comptait les passages non payés au lieu de les ignorer) (04/10/2025)
- [x] Corriger le graphique pie de la home page admin qui affichait les passages utilisateur au lieu de tous les passages (04/10/2025)
---
<div style="page-break-after: always;"></div>
## PRIORITÉ 3 - Interface utilisateur
### 💬 Module de messagerie
**Visibilité des actions**
- [ ] Améliorer la visibilité du bouton "Envoyer un message"
- [ ] Augmenter l'épaisseur de la police pour une meilleure lisibilité
### 🎨 Ergonomie des formulaires
**Textes d'aide**
- [ ] Améliorer les textes d'aide (helpers) dans les fiches membres
- [ ] Rendre les textes plus clairs et explicites
### 🏗️ Architecture et refactoring
**Simplification du layout**
- [x] Corriger le fond dégradé qui affichait rouge en mode user pour les admins (05/10/2025)
- [ ] Simplifier l'architecture DashboardLayout et AppScaffold (actuellement redondants avec fonds dupliqués)
- [ ] Refactoriser pour séparer clairement les responsabilités (fond, navigation, restrictions d'accès)
---
## RESTRICTIONS D'ACCÈS
### Mode Admin
- [ ] L'accès administrateur doit être limité au web uniquement
- [ ] Pas d'accès admin sur mobile pour des raisons de sécurité
### Connexion multi-rôles
- [ ] Permettre à un utilisateur de choisir son rôle (admin/membre) à la connexion
- [ ] Un admin (fkRole==2) doit pouvoir se connecter en tant qu'utilisateur également
---
<div style="page-break-after: always;"></div>
## MODE SUPER ADMIN
### Gestion des amicales
**Performance**
- [ ] Corriger le ralentissement après 3 suppressions d'amicales consécutives
- [ ] Optimiser le processus de purge des données
**Filtres et visualisation**
- [ ] Ajouter des filtres sur la liste des amicales
- [ ] Implémenter un mode démo pour les présentations
- [ ] Distinguer visuellement les amicales actives (ayant réglé) des autres
### Gestion des opérations
- [ ] Si suppression de l'opération active, réactiver automatiquement l'opération précédente
---
## PROCESSUS D'INSCRIPTION
### Double envoi d'emails
Envoyer 2 emails séparés lors de l'inscription :
- [ ] **Email 1** : Identifiant de connexion
- [ ] **Email 2** : Mot de passe avec informations complémentaires
_Bénéfice : Sécurité renforcée et meilleure traçabilité_
---
<div style="page-break-after: always;"></div>
## MODULE STRIPE
### Paiement en ligne dans les passages
**Fonctionnalité principale**
- [ ] Intégrer la gestion du paiement en ligne directement dans le formulaire de passage
- [ ] Disponible uniquement si l'amicale a un compte Stripe actif
**Caractéristiques**
- [ ] Détection automatique du statut Stripe de l'amicale
- [ ] Option "Paiement par carte" dans les modes de règlement
- [ ] Interface de paiement sécurisée intégrée
- [ ] Génération automatique du reçu après paiement
### Mode hors connexion
- [ ] Étudier les possibilités de paiement Stripe en mode hors ligne
- [ ] Permettre les paiements même sans connexion internet stable
### Tests et développement
**Paiement sans contact (Tap to Pay)**
- [ ] Mettre en place un environnement de test pour le paiement sans contact
- [ ] Documenter la procédure de test pour Tap to Pay
- [ ] Vérifier la compatibilité des appareils de test disponibles
---
## PLANNING PRÉVISIONNEL
### 📅 Sprint 1 : 12-19 septembre 2025
**Priorité 1 - Corrections critiques**
| Date | Version | Tâches |
| ------------------------- | ------- | --------------------------------------------------- |
| Vendredi 12/09 | v3.2.5 | Analyse et priorisation des bugs critiques |
| Lundi 15 - Mardi 16/09 | v3.2.6 | Correction problème F5 et déconnexion |
| Mercredi 17/09 | v3.2.7 | Fix génération mots de passe et champs obligatoires |
| Jeudi 18 - Vendredi 19/09 | v3.2.8 | Correction sauvegarde secteurs + tests |
### 📅 Sprint 2 : 22-26 septembre 2025
**Priorité 2 - Fonctionnalités**
| Date | Version | Tâches |
| ---------------------- | ------- | --------------------------------------------------- |
| Lundi 22 - Mardi 23/09 | v3.2.9 | Liste membres avec statistiques + filtres |
| Mercredi 24/09 | v3.3.0 | Historique avec sélection membre et dates |
| Jeudi 25/09 | v3.3.1 | Carte (zoom max, géolocalisation terrain) |
| Vendredi 26/09 | v3.3.2 | Intégration paiement Stripe dans formulaire passage |
### 📅 Sprint 3 : 29 septembre - 03 octobre 2025
**Finalisation**
| Date | Version | Tâches |
| ------------------ | ---------- | ---------------------------------------- |
| Lundi 29/09 | v3.4.0 | Interface (chat, police, ergonomie) |
| Mardi 30/09 | v3.4.1 | Mode Super Admin (filtres, performances) |
| Mercredi 01/10 | v3.4.2 | Tests d'intégration complets |
| Jeudi 02/10 | v3.4.3 | Recette client et corrections finales |
| **Vendredi 03/10** | **v3.4.4** | **LIVRAISON FINALE** |
### 📅 08 octobre 2025 : CONGRÈS
- Version de production déployée et stable
- Formation utilisateurs effectuée
- Documentation finalisée
---
<div style="page-break-after: always;"></div>
## POINT FINANCIER
### COÛT TOTAL HT Hors maintenance : 36.000 euros HT
### Factures Réglées
| Date | Réglée | Montant Applicatif |
| ------------------------------------- | ------ | ------------------ |
| 08/04 | Oui | 4.200 € HT |
| 26/05 | Oui | 3.880 € HT |
| 30/06 | Oui | 3.880 € HT |
| 26/08 | Oui | 3.880 € HT |
| | | Total 15.840 € HT |
| ------------------------------------- |
### Prochaines Factures
| Date | Réglée | Montant Applicatif |
| ------------------------------------- | ------ | ------------------ |
| 12/09 | Non | 3.360 € HT |
| 10/10 | Non | 3.360 € HT |
| 08/11 | Non | 3.360 € HT |
| 06/12 | Non | 3.360 € HT |
| 04/01 | Non | 3.360 € HT |
| 02/02 | Non | 3.360 € HT |
| ------------------------------------- |
---
<div style="page-break-after: always;"></div>
## UPGRADES PACKAGES FLUTTER
### 📊 État des packages (Octobre 2025)
L'analyse `flutter pub outdated` a révélé plusieurs packages nécessitant des mises à jour, dont un package discontinué critique.
### 🔴 Phase 1 - Correction package discontinué (URGENT)
**Statut : ✅ TERMINÉ (06/10/2025)**
| Package | Action | Ancienne version | Nouvelle version |
|---------|--------|------------------|------------------|
| `dio_cache_interceptor_hive_store` | ❌ Suppression (discontinué) | 3.2.2 | - |
| `http_cache_hive_store` | ✅ Ajout (remplacement) | - | 5.0.0 |
| `flutter_map_cache` | ⬆️ Mise à jour | 1.5.2 | 2.0.0+1 |
**Fichiers modifiés :**
- `pubspec.yaml` : Remplacement des dépendances
- `lib/presentation/widgets/mapbox_map.dart` : Import mis à jour
**Tests requis :**
- [x] Affichage carte web
- [x] Affichage carte mobile
- [x] Cache des tuiles mobile
- [x] Mode terrain
### 🟡 Phase 2 - Mises à jour importantes (PLANIFIÉ)
**Statut : ⏳ EN ATTENTE**
#### Cartes et géolocalisation
| Package | Actuelle | Cible | Breaking Changes |
|---------|----------|-------|------------------|
| `flutter_map` | 6.2.1 | 8.2.2 | ⚠️ Oui (v7, v8) |
| `geolocator` | 12.0.0 | 14.0.2 | Possible |
#### Device Info & Permissions
| Package | Actuelle | Cible | Importance |
|---------|----------|-------|------------|
| `device_info_plus` | 9.1.2 | 12.1.0 | ⭐⭐⭐ Tap to Pay |
| `battery_plus` | 4.1.0 | 7.0.0 | ⭐⭐ |
| `connectivity_plus` | 5.0.2 | 7.0.0 | ⭐⭐ |
| `sensors_plus` | 3.1.0 | 7.0.0 | ⭐⭐⭐ Mode boussole |
| `permission_handler` | 11.4.0 | 12.0.1 | ⭐⭐⭐ |
**Points d'attention :**
- `flutter_map 8.x` : Breaking changes majeurs v6 → v8
- `device_info_plus` : Vérifier compatibilité DeviceInfoService
- Tests complets requis : cartes, géolocalisation, mode terrain
### 🟢 Phase 3 - Mises à jour secondaires (PLANIFIÉ)
**Statut : ⏳ EN ATTENTE**
| Package | Actuelle | Cible | Note |
|---------|----------|-------|------|
| `syncfusion_flutter_charts` | 30.2.7 | 31.1.22 | Mineure |
| `package_info_plus` | 4.2.0 | 8.3.1 | Vérifier compatibilité |
**Packages à jour :**
-`dio: 5.9.0`
-`go_router: 16.2.4`
-`hive: 2.2.3`
-`flutter_stripe: 12.0.2`
-`mek_stripe_terminal: 4.6.0`
### 📅 Planning des upgrades
| Phase | Période prévue | Priorité | Effort |
|-------|----------------|----------|--------|
| Phase 1 | ✅ 06/10/2025 | 🔴 Critique | 1h |
| Phase 2 | 10-15/10/2025 | 🟡 Important | 4-6h |
| Phase 3 | 20-25/10/2025 | 🟢 Mineur | 2-3h |
---
_Document généré le 11 septembre 2025_
_Dernière mise à jour le 06 octobre 2025_
_Ce document sera mis à jour régulièrement avec l'avancement des développements_
---
**GEOSECTOR** - Solution de gestion des distributions de calendriers Amicales de pompiers
© 2025 - Tous droits réservés

Binary file not shown.

View File

@@ -0,0 +1,578 @@
# TODO - Isolation complète des opérations
## 🎯 Objectif
Mettre en place une **isolation complète par opération** où chaque opération est totalement autonome et peut être supprimée indépendamment sans impacter les autres opérations ou la table centrale `users`.
## 📊 Architecture cible
```
operations (id: 850)
├── ope_users (id: 2500, fk_operation: 850, fk_user: 100)
│ ├── ope_users_sectors (fk_user: 2500 ← ope_users.id, fk_sector: 5400)
│ └── ope_pass (fk_user: 2500 ← ope_users.id, fk_sector: 5400)
└── ope_sectors (id: 5400, fk_operation: 850)
users (id: 100) ← table centrale (conservée même si opération supprimée)
```
---
## ✅ Tâche 1 : Modification du schéma SQL
### 📁 Fichier : `scripts/orga/fix_fk_constraints.sql`
### Actions
- [ ] **1.1** Tester le script SQL sur **dva_geo** (DEV)
```bash
incus exec dva-geo -- mysql rca_geo < /var/www/geosector/api/scripts/orga/fix_fk_constraints.sql
```
- [ ] **1.2** Vérifier les contraintes après exécution :
```sql
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'rca_geo'
AND TABLE_NAME IN ('ope_users_sectors', 'ope_pass')
AND COLUMN_NAME = 'fk_user';
```
Résultat attendu :
- `ope_users_sectors.fk_user → ope_users.id`
- `ope_pass.fk_user → ope_users.id`
- [ ] **1.3** Appliquer sur **rca_geo** (RECETTE) après validation sur dva_geo
- [ ] **1.4** Appliquer sur **pra_geo** (PRODUCTION) après validation sur rca_geo
### ⚠️ Important
- Les données existantes doivent être **nettoyées avant** d'appliquer le script
- Ou bien : recréer toutes les données avec la nouvelle migration
- Les FK `ON DELETE CASCADE` supprimeront automatiquement `ope_users_sectors` et `ope_pass` quand `ope_users` est supprimé
---
## ✅ Tâche 2 : Correction du script de migration2
### 📁 Fichiers concernés
1. `scripts/migration2/php/lib/SectorMigrator.php`
2. `scripts/migration2/php/lib/PassageMigrator.php`
### Actions
#### 2.1 SectorMigrator.php - Migration de ope_users_sectors
- [ ] **Ligne 253** : Changer de `users.id` vers `ope_users.id`
```php
// ❌ AVANT
':fk_user' => $us['fk_user'], // ID de users (table centrale)
// ✅ APRÈS
':fk_user' => $userMapping[$us['fk_user']], // ID de ope_users (mapping)
```
#### 2.2 PassageMigrator.php - Migration de ope_pass
- [ ] **Ligne 64-67** : Vérifier le mapping existe
- [ ] **Ligne 77** : Passer `ope_users.id` au lieu de `users.id`
```php
// ❌ AVANT (ligne 77)
$newPassId = $this->insertPassage($passage, $newOperationId, $newOpeSectorId, $passage['fk_user']);
// ✅ APRÈS
$newOpeUserId = $userMapping[$passage['fk_user']];
$newPassId = $this->insertPassage($passage, $newOperationId, $newOpeSectorId, $newOpeUserId);
```
- [ ] **Ligne 164** : Utiliser le paramètre `$userId` qui sera maintenant `ope_users.id`
```php
// ❌ AVANT
':fk_user' => $userId, // ID de users (table centrale)
// ✅ APRÈS (le paramètre $userId contiendra déjà ope_users.id)
':fk_user' => $userId, // ID de ope_users
```
- [ ] **Ligne 71** : Corriger `verifyUserSectorAssociation` pour vérifier avec `ope_users.id`
```php
// ❌ AVANT
if (!$this->verifyUserSectorAssociation($newOperationId, $passage['fk_user'], $newOpeSectorId)) {
// ✅ APRÈS
if (!$this->verifyUserSectorAssociation($newOperationId, $newOpeUserId, $newOpeSectorId)) {
```
#### 2.3 Tester la migration complète
- [ ] **Sur dva_geo** : Vider les données d'une entité et relancer la migration
```bash
php php/migrate_from_backup.php --mode=entity --entity-id=5
```
- [ ] **Vérifier** dans la base que :
- `ope_users_sectors.fk_user` contient des IDs de `ope_users.id`
- `ope_pass.fk_user` contient des IDs de `ope_users.id`
- Les valeurs correspondent bien au mapping
- [ ] **Vérifier** qu'on peut supprimer une opération et que tout part avec (CASCADE)
```sql
DELETE FROM operations WHERE id = 850;
-- Doit supprimer automatiquement :
-- - ope_users (ON DELETE CASCADE depuis operations)
-- - ope_users_sectors (ON DELETE CASCADE depuis ope_users)
-- - ope_pass (ON DELETE CASCADE depuis ope_users)
-- - ope_sectors (ON DELETE CASCADE depuis operations)
```
---
## ✅ Tâche 3 : Vérifications API
### Impact sur les endpoints API
#### 3.1 Vérifier les requêtes utilisant `ope_pass.fk_user`
- [ ] **Rechercher** tous les endpoints qui lisent `ope_pass.fk_user`
```bash
grep -r "ope_pass.*fk_user" src/Controllers/
grep -r "fk_user.*ope_pass" src/Controllers/
```
- [ ] **Vérifier** que ces endpoints :
- Font-ils des JOIN avec `users` via `ope_pass.fk_user` ?
- Si OUI : Ajouter un JOIN via `ope_users` :
```sql
-- ❌ AVANT
SELECT op.*, u.encrypted_name
FROM ope_pass op
JOIN users u ON op.fk_user = u.id
-- ✅ APRÈS
SELECT op.*, u.encrypted_name
FROM ope_pass op
JOIN ope_users ou ON op.fk_user = ou.id
JOIN users u ON ou.fk_user = u.id
```
#### 3.2 Vérifier les requêtes utilisant `ope_users_sectors.fk_user`
- [ ] **Rechercher** tous les endpoints qui lisent `ope_users_sectors.fk_user`
```bash
grep -r "ope_users_sectors.*fk_user" src/Controllers/
```
- [ ] **Vérifier** la même chose : si JOIN avec `users`, ajouter passage par `ope_users`
#### 3.3 Endpoints probablement concernés
À vérifier :
- [ ] `OperationController` - Liste des utilisateurs d'une opération
- [ ] `PassageController` - Liste/détails des passages
- [ ] `SectorController` - Liste des secteurs avec utilisateurs affectés
- [ ] Tout endpoint retournant des statistiques par utilisateur
---
## ✅ Tâche 4 : Corrections API - Response JSON Login
### Impact sur la réponse JSON du login
#### 4.1 Groupe `users_sectors` - Ajouter `ope_user_id`
**Problème identifié** : Flutter reçoit `users_sectors` avec `id` (users.id) mais les `passages` ont `fk_user` (ope_users.id). Le mapping est impossible.
**Solution** : Modifier la requête dans `LoginController.php` (lignes 426 et 1181) pour retourner les deux IDs :
```sql
-- ✅ APRÈS
SELECT DISTINCT
u.id as user_id, -- users.id (table centrale, pour gestion membres)
ou.id as ope_user_id, -- ope_users.id (pour lier avec passages/sectors)
ou.first_name,
u.encrypted_name,
u.sect_name,
us.fk_sector
FROM users u
JOIN ope_users ou ON u.id = ou.fk_user
JOIN ope_users_sectors us ON ou.id = us.fk_user AND ou.fk_operation = us.fk_operation
WHERE us.fk_sector IN ($sectorIdsString)
AND us.fk_operation = ?
AND us.chk_active = 1
AND u.chk_active = 1
AND u.id != ?
```
**Résultat JSON attendu** :
```json
{
"user_id": 123, // users.id (pour gestion des membres dans l'interface)
"ope_user_id": 50, // ope_users.id (pour lier avec passages.fk_user et sectors)
"first_name": "Jane",
"name": "Jane Smith",
"sect_name": "Smith",
"fk_sector": 456
}
```
**Usage Flutter** :
```dart
// Trouver les passages d'un utilisateur
passages.where((p) => p.fkUser == usersSectors[i].opeUserId) // ✅ OK
```
- [ ] **Modifier** `LoginController.php` ligne 426 (méthode `login()`)
- [ ] **Modifier** `LoginController.php` ligne 1181 (méthode `checkSession()`)
- [ ] **Tester** la réponse JSON du login en mode admin
---
## ✅ Tâche 5 : Vérifications Flutter - Gestion des IDs
### Impact sur l'application mobile
#### 5.1 Modèles de données
- [x] **Vérifier** le modèle `UserSector` (ou équivalent)
- Ajouter le champ `opeUserId` (int) pour stocker `ope_users.id`
- Conserver `userId` (int) pour stocker `users.id`
- ✅ **Fait** : `UserSectorModel` modifié avec les champs `userId` et `opeUserId`
- ✅ **Fait** : Adaptateurs Hive régénérés avec `build_runner`
- [x] **Vérifier** le modèle `Passage` (ou équivalent)
- Le champ `fkUser` pointe maintenant vers `ope_users.id`
- ✅ **Fait** : `PassageModel.fkUser` pointe déjà vers `ope_users.id`
- [x] **Vérifier** le modèle `User`
- Ajouter le champ `opeUserId` (int?) pour stocker l'ID de l'utilisateur dans `ope_users`
- ✅ **Fait** : `UserModel` modifié avec `opeUserId` (@HiveField(20))
- ✅ **Fait** : `CurrentUserService` expose `opeUserId` via getter
#### 5.2 Gestion des secteurs (Mode Admin)
- [x] **Création de secteur**
- L'API crée dans `ope_sectors`
- Attribution des users : utiliser `ope_user_id` (pas `user_id`)
- Endpoint : `POST /api/sectors`
- Body : `{ ..., users: [50, 51, 52] }` ← IDs de `ope_users`
- ✅ **Fait** : `SectorDialog` utilise `userSector.opeUserId` pour l'attribution
- ✅ **Fait** : Liste dédupliquée des membres depuis `UserSectorModel`
- [x] **Modification de secteur**
- Attribution des users : utiliser `ope_user_id`
- Endpoint : `PUT /api/sectors/:id`
- Body : `{ ..., users: [50, 51, 52] }` ← IDs de `ope_users`
- ✅ **Fait** : `SectorDialog` utilise `userSector.opeUserId` pour l'attribution
- [ ] **Suppression de secteur**
- L'API supprime dans `ope_pass`, `ope_users_sectors` et `ope_sectors`
- CASCADE gère automatiquement les dépendances
- Endpoint : `DELETE /api/sectors/:id`
#### 5.3 Gestion des membres (Mode Admin)
- [ ] **Création de membre**
- L'API crée dans `users` (table centrale)
- L'API crée aussi dans `ope_users` pour l'opération active
- **Réponse attendue** :
```json
{
"status": "success",
"user": {
"id": 123, // users.id
"ope_user_id": 50, // ope_users.id (nouveau)
"first_name": "John",
"name": "John Doe",
...
}
}
```
- Endpoint : `POST /api/users`
- Flutter stocke les 2 IDs : `userId` et `opeUserId`
- [ ] **Modification de membre**
- L'API met à jour `users` (table centrale)
- L'API met à jour aussi `ope_users` pour l'opération active
- Endpoint : `PUT /api/users/:id`
- [ ] **Suppression de membre**
- L'API supprime de `ope_users` (opération active)
- L'API supprime de `users` (table centrale)
- CASCADE supprime automatiquement `ope_users_sectors` et `ope_pass`
- Endpoint : `DELETE /api/users/:id?transfer_to=XX`
#### 5.4 Gestion des passages (Mode Admin & User)
- [x] **Création de passage**
- Attribution automatique du `ope_sectors.id` le plus proche
- Attribution du `ope_users.id` (utilisateur connecté ou sélectionné)
- Endpoint : `POST /api/passages`
- Body : `{ ..., fk_user: 50, fk_sector: 456 }` ← IDs de `ope_users` et `ope_sectors`
- ✅ **Fait** : `PassageRepository.createPassage()` utilise `CurrentUserService.instance.opeUserId`
- [x] **Modification de passage**
- Attribution du `ope_users.id` si changement d'utilisateur
- Endpoint : `PUT /api/passages/:id`
- Body : `{ ..., fk_user: 50 }` ← ID de `ope_users`
- ✅ **Fait** : `PassageRepository.updatePassage()` utilise `CurrentUserService.instance.opeUserId`
- ✅ **Fait** : Mode offline et online correctement implémentés
- [ ] **Suppression de passage**
- L'API supprime dans `ope_pass`
- Endpoint : `DELETE /api/passages/:id`
#### 5.5 Interface Flutter - Mapping des IDs
**Scénarios à gérer** :
1. **Affichage des secteurs avec utilisateurs affectés** :
```dart
// Utiliser usersSectors[i].opeUserId pour lier avec passages
final userPassages = passages.where((p) =>
p.fkUser == usersSectors[i].opeUserId &&
p.fkSector == sector.id
).toList();
```
- ✅ **Fait** : `ActivityChart` filtre par secteurs assignés (pas par userId)
- ✅ **Fait** : `MapPage` utilise `userSector.opeUserId` pour filtrer les secteurs
2. **Attribution d'un passage à un utilisateur** :
```dart
// Envoyer ope_user_id dans la requête API
await apiService.createPassage({
...passageData,
'fk_user': userSector.opeUserId, // ope_users.id
'fk_sector': sector.id
});
```
- ✅ **Fait** : `PassageRepository` utilise `CurrentUserService.instance.opeUserId`
3. **Affichage du nom d'un utilisateur depuis un passage** :
```dart
// Chercher dans usersSectors avec ope_user_id
final userSector = usersSectors.firstWhere(
(us) => us.opeUserId == passage.fkUser,
orElse: () => null
);
final userName = userSector?.name ?? 'Inconnu';
```
- ✅ **Fait** : `PaymentSummaryCard` utilise `opeUserId` pour filtrer les règlements
- ✅ **Fait** : `HistoryPage` utilise `opeUserId` pour filtrer et vérifier les permissions
- ✅ **Fait** : `UserFieldModePage` compare `passage.fkUser` avec `opeUserId`
4. **Gestion des membres** :
```dart
// Conserver les 2 IDs lors de la création
final newMember = await apiService.createUser(userData);
membres.add(Member(
userId: newMember['id'], // users.id
opeUserId: newMember['ope_user_id'], // ope_users.id
...
));
```
- ⚠️ **À tester** : Vérifier la réception de `ope_user_id` dans les réponses API
5. **Statistiques par utilisateur** :
- ✅ **Fait** : `PassageRepository.getStatisticsByUser()` utilise `passage.fkUser` (ope_users.id)
- ✅ **Fait** : Variable renommée `opeUserId` pour clarté
6. **Services et chargement de données** :
- ✅ **Fait** : `DataLoadingService` utilise `opeUserId` pour les clés Hive des secteurs
- ✅ **Fait** : `CurrentUserService.opeUserId` accessible globalement
#### 5.6 Tests d'affichage
- [ ] Tester l'affichage des passages avec noms d'utilisateurs
- [ ] Tester l'affichage des secteurs avec utilisateurs affectés
- [ ] Tester la création d'un membre (vérifier que les 2 IDs sont reçus)
- [ ] Tester la suppression d'un membre (vérifier le transfert de passages)
- [ ] Tester la création d'un secteur avec attribution d'utilisateurs
- [ ] Tester la création d'un passage avec attribution d'utilisateur
- [ ] Tester la suppression d'une opération (doit tout nettoyer)
---
### 📝 Modifications Flutter effectuées (2025-01-23)
#### Fichiers modifiés
1. **`lib/core/data/models/user_sector_model.dart`** (lignes 11-50)
- Ajout du champ `opeUserId` (@HiveField(5))
- Renommage du champ `id` → `userId` (@HiveField(0))
- Mise à jour de `fromJson()` pour parser les deux IDs
- Mise à jour de `toJson()` et `copyWith()`
2. **`lib/core/data/models/user_model.dart`** (ligne 122)
- Ajout du champ `opeUserId` (@HiveField(20))
- Mise à jour de `fromJson()`, `toJson()` et `copyWith()`
3. **`lib/core/services/current_user_service.dart`** (ligne 24)
- Ajout du getter `opeUserId` pour accès global
4. **`lib/core/services/data_loading_service.dart`** (ligne 451)
- Utilisation de `userSector.opeUserId` pour les clés Hive
5. **`lib/presentation/dialogs/sector_dialog.dart`** (lignes 86, 538-542, 999-1007)
- Changement de source : `MembreModel` → `UserSectorModel`
- Utilisation de `opeUserId` pour la sélection des membres
- Déduplication des membres (un user peut être sur plusieurs secteurs)
6. **`lib/presentation/pages/history_page.dart`** (lignes 62-63, 126, 997-1009, 1023, 1608, 1629)
- Ajout de `currentOpeUserId` (ligne 62)
- Utilisation de `UserSectorModel` avec déduplication
- Comparaison `passage.fkUser == currentOpeUserId` pour filtrage et permissions
7. **`lib/presentation/user/user_field_mode_page.dart`** (ligne 999-1002)
- Correction : `passage.fkUser == userRepository.getCurrentUser()?.opeUserId`
8. **`lib/presentation/widgets/charts/payment_summary_card.dart`** (ligne 425, 428-430)
- Utilisation de `currentUser?.opeUserId` pour filtrer les règlements utilisateur
9. **`lib/core/repositories/passage_repository.dart`** (lignes 105, 384-388, 422-426, 577)
- Renommage du paramètre : `getPassagesByUser(int opeUserId)`
- Utilisation de `CurrentUserService.instance.opeUserId` pour création/modification
- Renommage de variable : `userId` → `opeUserId` dans les statistiques
10. **`lib/presentation/pages/map_page.dart`** (lignes 792-795)
- **ERREUR CRITIQUE CORRIGÉE** : `us.id` → `us.opeUserId`
- Utilisation de `CurrentUserService.instance.opeUserId`
11. **`lib/presentation/pages/home_page.dart`** (lignes 22-32)
- Suppression de variable inutilisée : `currentUserId`
#### Adaptateurs générés
- **`lib/core/data/models/user_sector_model.g.dart`** - Régénéré avec `build_runner`
- **`lib/core/data/models/user_model.g.dart`** - Régénéré avec `build_runner`
#### Widgets vérifiés (pas de modification nécessaire)
- `PassageSummaryCard` - Affiche tous les passages déjà filtrés par l'API
- `ActivityChart` - Filtre par secteurs assignés, pas par userId
- `SectorDistributionCard` - Affiche tous les secteurs sans filtrage utilisateur
- `MembersBoardPassages` - `membre.id` représente déjà `ope_users.id`
#### Analyse statique
```bash
flutter analyze
```
- ✅ **0 erreur de compilation**
- 33 avertissements de style (info uniquement)
---
## 📋 Ordre d'exécution recommandé
1. ✅ **Corriger le code de migration2** (PHP)
2. ✅ **Tester sur dva_geo** avec schéma modifié
3. ✅ **Vérifier l'API** sur dva_geo
4. ✅ **Vérifier Flutter** avec dva_geo
5. 🚀 **Déployer le schéma SQL** sur rca_geo
6. 🚀 **Déployer le code** sur rca_geo
7. ✅ **Tester en recette**
8. 🚀 **Déployer en production** (pra_geo)
---
## 🔍 Requêtes SQL utiles pour vérification
### Vérifier les contraintes FK actuelles
```sql
SELECT
TABLE_NAME,
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND (TABLE_NAME = 'ope_pass' OR TABLE_NAME = 'ope_users_sectors')
AND COLUMN_NAME = 'fk_user';
```
### Vérifier l'intégrité des données après migration
```sql
-- Vérifier que tous les fk_user de ope_pass existent dans ope_users
SELECT COUNT(*) as orphans
FROM ope_pass op
LEFT JOIN ope_users ou ON op.fk_user = ou.id
WHERE ou.id IS NULL;
-- Résultat attendu : 0
-- Vérifier que tous les fk_user de ope_users_sectors existent dans ope_users
SELECT COUNT(*) as orphans
FROM ope_users_sectors ous
LEFT JOIN ope_users ou ON ous.fk_user = ou.id
WHERE ou.id IS NULL;
-- Résultat attendu : 0
```
### Tester la suppression en cascade
```sql
-- Compter avant suppression
SELECT
(SELECT COUNT(*) FROM ope_users WHERE fk_operation = 850) as ope_users_count,
(SELECT COUNT(*) FROM ope_users_sectors WHERE fk_operation = 850) as ope_users_sectors_count,
(SELECT COUNT(*) FROM ope_pass WHERE fk_operation = 850) as ope_pass_count,
(SELECT COUNT(*) FROM ope_sectors WHERE fk_operation = 850) as ope_sectors_count;
-- Supprimer l'opération
DELETE FROM operations WHERE id = 850;
-- Vérifier que tout a été supprimé (doit retourner 0 partout)
SELECT
(SELECT COUNT(*) FROM ope_users WHERE fk_operation = 850) as ope_users_count,
(SELECT COUNT(*) FROM ope_users_sectors WHERE fk_operation = 850) as ope_users_sectors_count,
(SELECT COUNT(*) FROM ope_pass WHERE fk_operation = 850) as ope_pass_count,
(SELECT COUNT(*) FROM ope_sectors WHERE fk_operation = 850) as ope_sectors_count;
```
---
## 📝 Notes importantes
### Avantages de cette architecture
✅ **Isolation complète** : Supprimer une opération supprime tout (ope_users, secteurs, passages)
✅ **Performance** : Pas de jointures complexes avec la table centrale `users`
✅ **Historique** : Les données d'une opération sont figées dans le temps
✅ **Simplicité** : Requêtes plus simples, moins de risques d'incohérences
### Implications
⚠️ **Duplication** : Un utilisateur travaillant sur 3 opérations aura 3 entrées dans `ope_users`
⚠️ **Taille** : La table `ope_users` sera plus volumineuse
⚠️ **Jointures** : Pour remonter aux infos de la table `users`, il faut passer par `ope_users.fk_user`
### Rétrocompatibilité
❌ Ce changement **CASSE** la compatibilité avec les données existantes
✅ Nécessite une **re-migration complète** de toutes les entités après modification du schéma
✅ Ou bien : script de transformation des données existantes (plus complexe)
---
## 🎯 Statut
- [ ] Schéma SQL modifié sur dva_geo
- [ ] Code migration2 corrigé
- [ ] API vérifiée et corrigée
- [x] **Flutter vérifié et corrigé** ✅ (2025-01-23)
- Modèles de données mis à jour (UserSectorModel, UserModel)
- Services mis à jour (CurrentUserService, DataLoadingService)
- Repositories mis à jour (PassageRepository)
- Pages et widgets mis à jour (11 fichiers)
- Adaptateurs Hive régénérés
- Analyse statique : 0 erreur
- [ ] Tests complets sur dva_geo (en attente API)
- [ ] Déploiement rca_geo
- [ ] Déploiement pra_geo

View File

@@ -0,0 +1,395 @@
# TODO - Cache Web pour tuiles Mapbox
## Contexte
**Problème** : En PROD, les admins travaillent intensivement sur la plateforme web et la carte. Actuellement, les tuiles Mapbox ne sont pas mises en cache sur le web, ce qui provoque :
- Téléchargements répétés des mêmes tuiles
- Consommation excessive de bande passante
- Navigation peu fluide
- Coûts API Mapbox potentiellement élevés
**Cause** : Mapbox envoie des headers `Cache-Control: no-cache` qui empêchent le cache navigateur par défaut, et `flutter_map_cache` ne supporte pas la plateforme web.
**Solution** : Implémenter un cache manuel des tuiles via IndexedDB en Dart, uniquement pour le web.
## Architecture technique
### Flux de données
```
flutter_map demande une tuile
CachedWebTileProvider (nouveau)
WebTileCacheService.getTile()
Vérifie IndexedDB
┌─────────────┴─────────────┐
│ │
Cache HIT Cache MISS
│ │
Retourne image Télécharge depuis Mapbox
depuis IndexedDB ↓
Stocke dans IndexedDB
Retourne image
```
### Composants
1. **WebTileCacheService** (Singleton)
- Gère IndexedDB pour stocker les tuiles
- Méthodes : `getTile()`, `storeTile()`, `clearCache()`, `clearExpiredTiles()`
- Durée de cache : 30 jours (aligné avec mobile/desktop)
2. **CachedWebTileProvider** (Custom TileProvider)
- Implémente `TileProvider` de `flutter_map`
- Utilise `WebTileCacheService` pour récupérer/stocker
- Fallback sur téléchargement direct si erreur
3. **MapboxMap** (Modifié)
- Utilise `CachedWebTileProvider` sur web
- Garde `HiveCacheStore` sur mobile/desktop
## Fichiers à créer/modifier
### ✅ Fichiers à créer
#### 1. `lib/core/services/web_tile_cache_service.dart`
Service singleton pour gérer le cache IndexedDB des tuiles Mapbox.
**Responsabilités** :
- Initialiser la base IndexedDB `mapbox_tiles_cache`
- Stocker les tuiles avec clé unique (URL de la tuile)
- Récupérer les tuiles depuis le cache
- Gérer l'expiration (30 jours)
- Nettoyer les tuiles expirées
**Structure IndexedDB** :
```
Database: mapbox_tiles_cache
ObjectStore: tiles
- key: String (URL de la tuile)
- value: Object {
data: Uint8List (bytes de l'image)
timestamp: int (millisecondsSinceEpoch)
}
```
**API publique** :
```dart
class WebTileCacheService {
static final instance = WebTileCacheService._();
Future<void> initialize();
Future<Uint8List?> getTile(String url);
Future<void> storeTile(String url, Uint8List data);
Future<void> clearCache();
Future<void> clearExpiredTiles();
}
```
**Dépendances** :
- `dart:indexed_db` (natif web)
- `dart:typed_data`
- `package:flutter/foundation.dart` (kIsWeb)
#### 2. `lib/presentation/widgets/cached_web_tile_provider.dart`
TileProvider custom qui utilise le cache IndexedDB.
**Responsabilités** :
- Implémenter `TileProvider` de `flutter_map`
- Vérifier le cache avant téléchargement
- Télécharger et stocker si absent du cache
- Fallback sur comportement par défaut si erreur
**API publique** :
```dart
class CachedWebTileProvider extends TileProvider {
final String accessToken;
final String styleId;
CachedWebTileProvider({
required this.accessToken,
required this.styleId,
});
@override
ImageProvider getImage(TileCoordinates coordinates, TileLayer options);
}
```
**Logique** :
1. Construire l'URL de la tuile
2. Appeler `WebTileCacheService.getTile(url)`
3. Si trouvée → `MemoryImage(cachedData)`
4. Sinon → Télécharger → Stocker → `MemoryImage(downloadedData)`
**Dépendances** :
- `flutter_map`
- `WebTileCacheService`
- `dart:typed_data`
- `package:http/http.dart` (ou `Dio` si préféré)
### ✅ Fichiers à modifier
#### 3. `lib/presentation/widgets/mapbox_map.dart`
Modifier pour utiliser le nouveau provider sur web.
**Modifications** :
- Lignes 96-139 : Refactoriser `_initializeCache()`
- Utiliser `CachedWebTileProvider` quand `kIsWeb == true`
- Garder `HiveCacheStore` pour mobile/desktop
- Initialiser `WebTileCacheService` au démarrage sur web
**Avant** (lignes 98-103) :
```dart
if (kIsWeb) {
// Pas de cache sur Web (non supporté)
setState(() {
_cacheInitialized = true;
});
return;
}
```
**Après** :
```dart
if (kIsWeb) {
// Cache manuel via IndexedDB
await WebTileCacheService.instance.initialize();
setState(() {
_cacheInitialized = true;
});
return;
}
```
**TileLayer config** (ligne ~180) :
```dart
TileLayer(
urlTemplate: 'https://api.mapbox.com/styles/v1/mapbox/$styleId/tiles/256/{z}/{x}/{y}@2x?access_token=$accessToken',
tileProvider: kIsWeb
? CachedWebTileProvider(
accessToken: accessToken,
styleId: styleId,
)
: (_cacheStore != null
? CachedTileProvider(store: _cacheStore!)
: NetworkTileProvider()),
// ...
),
```
## Étapes d'implémentation
### Phase 1 : Service de cache IndexedDB
**Tâche 1.1** : Créer `web_tile_cache_service.dart`
- [ ] Créer la classe singleton
- [ ] Implémenter `initialize()` avec création de la DB IndexedDB
- [ ] Implémenter `getTile(url)` avec vérification d'expiration
- [ ] Implémenter `storeTile(url, data)` avec timestamp
- [ ] Implémenter `clearCache()` pour vider toute la DB
- [ ] Implémenter `clearExpiredTiles()` pour le nettoyage automatique
- [ ] Ajouter des logs de debug (`debugPrint`)
**Tâche 1.2** : Gestion des erreurs
- [ ] Try/catch sur toutes les opérations IndexedDB
- [ ] Fallback gracieux si IndexedDB indisponible
- [ ] Logs d'erreur explicites
**Tâche 1.3** : Tests unitaires
- [ ] Tester `initialize()` crée bien la DB
- [ ] Tester `storeTile()` + `getTile()` roundtrip
- [ ] Tester expiration (mock timestamp)
- [ ] Tester `clearCache()`
### Phase 2 : TileProvider custom
**Tâche 2.1** : Créer `cached_web_tile_provider.dart`
- [ ] Créer la classe qui extend `TileProvider`
- [ ] Implémenter `getImage()` avec logique de cache
- [ ] Gérer le téléchargement des tuiles manquantes
- [ ] Stocker les tuiles téléchargées dans le cache
- [ ] Ajouter des logs de performance (cache hit/miss)
**Tâche 2.2** : Gestion des erreurs réseau
- [ ] Try/catch sur téléchargement
- [ ] Retry logic (3 tentatives max)
- [ ] Placeholder si échec total
- [ ] Logs d'erreur explicites
**Tâche 2.3** : Tests
- [ ] Tester avec tuile en cache → Pas de requête réseau
- [ ] Tester avec tuile absente → Téléchargement + stockage
- [ ] Tester avec erreur réseau → Fallback gracieux
### Phase 3 : Intégration dans MapboxMap
**Tâche 3.1** : Modifier `mapbox_map.dart`
- [ ] Importer `CachedWebTileProvider` et `WebTileCacheService`
- [ ] Modifier `_initializeCache()` pour initialiser le service web
- [ ] Modifier `TileLayer` pour utiliser le bon provider selon la plateforme
- [ ] Tester sur web que le nouveau provider est bien utilisé
**Tâche 3.2** : Validation visuelle
- [ ] Lancer l'app en mode web DEV
- [ ] Ouvrir DevTools → Console pour voir les logs
- [ ] Naviguer sur la carte → Vérifier logs "Cache MISS" au premier passage
- [ ] Revenir sur la même zone → Vérifier logs "Cache HIT"
- [ ] Vérifier dans DevTools → Application → IndexedDB que les tuiles sont stockées
**Tâche 3.3** : Tests de performance
- [ ] Mesurer le temps de chargement initial (sans cache)
- [ ] Mesurer le temps de chargement avec cache
- [ ] Vérifier qu'il n'y a plus de requêtes réseau pour les tuiles en cache
- [ ] Tester sur une session longue (plusieurs déplacements sur la carte)
### Phase 4 : Nettoyage et documentation
**Tâche 4.1** : Nettoyage automatique
- [ ] Appeler `clearExpiredTiles()` au démarrage de l'app (dans `initialize()`)
- [ ] Ajouter un bouton admin pour vider manuellement le cache (optionnel)
**Tâche 4.2** : Logs et monitoring
- [ ] Ajouter des statistiques de cache (hit rate, taille totale)
- [ ] Logger les performances (temps de chargement moyen)
**Tâche 4.3** : Documentation
- [ ] Commenter le code (dartdoc)
- [ ] Mettre à jour `FLOW-*.md` si nécessaire
- [ ] Ajouter section dans README technique
## Tests de validation
### Tests fonctionnels
1. **Cache vide → Premier chargement**
- [ ] Ouvrir l'app web en mode incognito
- [ ] Naviguer sur la carte
- [ ] Vérifier que les tuiles se chargent
- [ ] Vérifier les logs "Cache MISS" + "Storing tile"
2. **Cache rempli → Rechargement**
- [ ] Recharger la page (F5)
- [ ] Revenir sur la même zone de carte
- [ ] Vérifier que les tuiles se chargent instantanément
- [ ] Vérifier les logs "Cache HIT"
- [ ] Vérifier dans Network tab : pas de requêtes Mapbox pour les tuiles en cache
3. **Navigation étendue**
- [ ] Se déplacer sur plusieurs zones de la carte
- [ ] Revenir sur les zones précédentes
- [ ] Vérifier mix de "Cache HIT" et "Cache MISS"
4. **Gestion de l'expiration**
- [ ] Modifier temporairement l'expiration à 10 secondes (pour test)
- [ ] Charger des tuiles
- [ ] Attendre 15 secondes
- [ ] Recharger → Vérifier que les tuiles sont re-téléchargées
5. **Gestion d'erreur réseau**
- [ ] Désactiver le réseau en plein chargement
- [ ] Vérifier que les tuiles en cache s'affichent quand même
- [ ] Vérifier que les tuiles manquantes ne bloquent pas l'app
### Tests de performance
6. **Mesure de bande passante**
- [ ] Vider le cache
- [ ] Naviguer sur une zone avec ~100 tuiles visibles
- [ ] Noter la bande passante consommée (DevTools Network)
- [ ] Recharger la page et revenir sur la même zone
- [ ] Vérifier que la bande passante est ~0 KB
7. **Mesure de vitesse**
- [ ] Mesurer le temps de chargement initial : ____ ms
- [ ] Mesurer le temps de chargement avec cache : ____ ms
- [ ] Objectif : >50% de réduction
### Tests multi-plateformes
8. **Mobile/Desktop non régressé**
- [ ] Lancer sur Android → Vérifier que HiveCacheStore fonctionne toujours
- [ ] Lancer sur iOS → Vérifier que HiveCacheStore fonctionne toujours
- [ ] Vérifier les logs "Cache initialized with HiveCacheStore"
9. **Web multi-navigateurs**
- [ ] Tester sur Chrome
- [ ] Tester sur Firefox
- [ ] Tester sur Safari (si IndexedDB supporté)
- [ ] Tester sur Edge
## Checklist de validation finale
- [ ] Aucune nouvelle dépendance ajoutée au `pubspec.yaml`
- [ ] Code compatible web uniquement (guards `kIsWeb`)
- [ ] Mobile/Desktop non impactés (toujours HiveCacheStore)
- [ ] Logs de debug présents et clairs
- [ ] Gestion d'erreur complète (try/catch)
- [ ] Expiration automatique (30 jours)
- [ ] Performance mesurée et améliorée
- [ ] Tests manuels passés
- [ ] Code documenté (dartdoc)
- [ ] Pas de régression sur les autres plateformes
## Notes techniques
### Pourquoi IndexedDB et pas localStorage ?
- **localStorage** : Limite de 5-10 MB → Insuffisant pour des tuiles (1 tuile = ~60 KB)
- **IndexedDB** : Limite de ~50% de l'espace disque disponible → Largement suffisant
### Structure de la clé de cache
Format recommandé : `{styleId}_{z}_{x}_{y}@{scale}x`
Exemple : `streets-v11_15_16234_11378@2x`
Cela permet :
- Identification unique de chaque tuile
- Support multi-styles (streets, satellite, etc.)
- Support multi-résolutions (@1x, @2x)
### Taille estimée du cache
- 1 tuile Mapbox @2x : ~60 KB
- Carte complète niveau zoom 15 : ~500 tuiles
- Total pour une zone : ~30 MB
- IndexedDB peut stocker plusieurs GB → Pas de souci
### Maintenance du cache
Le nettoyage automatique (`clearExpiredTiles()`) s'exécute :
- Au démarrage de l'app
- Évite l'accumulation de vieilles tuiles
- Garde le cache sous contrôle
## Dépendances
### Packages Flutter (déjà présents)
- `flutter_map: ^7.0.2` → Fournit `TileProvider`
- `flutter_map_cache: ^1.5.1` → Pour mobile/desktop uniquement
### Bibliothèques Dart natives (aucun ajout)
- `dart:indexed_db` → Pour IndexedDB (web seulement)
- `dart:typed_data` → Pour manipuler les bytes
- `package:flutter/foundation.dart` → Pour `kIsWeb`
### Pas de nouvelle dépendance externe ✅
## Estimation
- **Temps de développement** : 2-3 heures
- **Complexité** : Moyenne
- **Risque** : Faible (fallback sur comportement actuel)
- **Gain** : Important pour les admins PROD
## Prochaine étape
**Attente de validation utilisateur** pour démarrer l'implémentation.

View File

@@ -1,602 +0,0 @@
-- -------------------------------------------------------------
-- TablePlus 6.4.8(608)
--
-- https://tableplus.com/
--
-- Database: geo_app
-- Generation Time: 2025-06-09 18:03:43.5140
-- -------------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE TABLE `chat_anonymous_users` (
`id` varchar(50) NOT NULL,
`device_id` varchar(100) NOT NULL,
`name` varchar(100) DEFAULT NULL,
`email` varchar(100) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`converted_to_user_id` int(10) unsigned DEFAULT NULL,
`metadata` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`metadata`)),
PRIMARY KEY (`id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_converted_user` (`converted_to_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_attachments` (
`id` varchar(50) NOT NULL,
`fk_message` varchar(50) NOT NULL,
`file_name` varchar(255) NOT NULL,
`file_path` varchar(500) NOT NULL,
`file_type` varchar(100) NOT NULL,
`file_size` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_message` (`fk_message`),
CONSTRAINT `fk_chat_attachments_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_audience_targets` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`target_type` enum('role','entity','all','combined') NOT NULL DEFAULT 'all',
`target_id` varchar(50) DEFAULT NULL,
`role_filter` varchar(20) DEFAULT NULL,
`entity_filter` varchar(50) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_type` (`target_type`),
CONSTRAINT `fk_chat_audience_targets_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_broadcast_lists` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_room` varchar(50) NOT NULL,
`name` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`fk_user_creator` int(10) unsigned NOT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user_creator` (`fk_user_creator`),
CONSTRAINT `fk_chat_broadcast_lists_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_messages` (
`id` varchar(50) NOT NULL,
`fk_room` varchar(50) NOT NULL,
`fk_user` int(10) unsigned DEFAULT NULL,
`sender_type` enum('user','anonymous','system') NOT NULL DEFAULT 'user',
`content` text DEFAULT NULL,
`content_type` enum('text','image','file') NOT NULL DEFAULT 'text',
`date_sent` timestamp NOT NULL DEFAULT current_timestamp(),
`date_delivered` timestamp NULL DEFAULT NULL,
`date_read` timestamp NULL DEFAULT NULL,
`statut` enum('envoye','livre','lu','error') NOT NULL DEFAULT 'envoye',
`is_announcement` tinyint(1) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_room` (`fk_room`),
KEY `idx_user` (`fk_user`),
KEY `idx_date` (`date_sent`),
KEY `idx_status` (`statut`),
KEY `idx_messages_unread` (`fk_room`,`statut`),
CONSTRAINT `fk_chat_messages_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_notifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_user` int(10) unsigned NOT NULL,
`fk_message` varchar(50) DEFAULT NULL,
`fk_room` varchar(50) DEFAULT NULL,
`type` varchar(50) NOT NULL,
`contenu` text DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`date_lecture` timestamp NULL DEFAULT NULL,
`statut` enum('non_lue','lue') NOT NULL DEFAULT 'non_lue',
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_room` (`fk_room`),
KEY `idx_statut` (`statut`),
KEY `idx_notifications_unread` (`fk_user`,`statut`),
CONSTRAINT `fk_chat_notifications_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE SET NULL,
CONSTRAINT `fk_chat_notifications_room` FOREIGN KEY (`fk_room`) REFERENCES `chat_rooms` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_offline_queue` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`operation_type` varchar(50) NOT NULL,
`operation_data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`operation_data`)),
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`processed_at` timestamp NULL DEFAULT NULL,
`status` enum('pending','processing','completed','failed') NOT NULL DEFAULT 'pending',
`error_message` text DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_participants` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`id_room` varchar(50) NOT NULL,
`id_user` int(10) unsigned DEFAULT NULL,
`anonymous_id` varchar(50) DEFAULT NULL,
`role` enum('administrateur','participant','en_lecture_seule') NOT NULL DEFAULT 'participant',
`date_ajout` timestamp NOT NULL DEFAULT current_timestamp(),
`notification_activee` tinyint(1) unsigned NOT NULL DEFAULT 1,
`last_read_message_id` varchar(50) DEFAULT NULL,
`via_target` tinyint(1) unsigned NOT NULL DEFAULT 0,
`can_reply` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uc_room_user` (`id_room`,`id_user`),
KEY `idx_room` (`id_room`),
KEY `idx_user` (`id_user`),
KEY `idx_anonymous_id` (`anonymous_id`),
KEY `idx_participants_active` (`id_room`,`id_user`,`notification_activee`),
CONSTRAINT `fk_chat_participants_room` FOREIGN KEY (`id_room`) REFERENCES `chat_rooms` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_read_messages` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`fk_message` varchar(50) NOT NULL,
`fk_user` int(10) unsigned NOT NULL,
`date_read` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uc_message_user` (`fk_message`,`fk_user`),
KEY `idx_message` (`fk_message`),
KEY `idx_user` (`fk_user`),
CONSTRAINT `fk_chat_read_messages_message` FOREIGN KEY (`fk_message`) REFERENCES `chat_messages` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `chat_rooms` (
`id` varchar(50) NOT NULL,
`type` enum('privee','groupe','liste_diffusion','broadcast','announcement') NOT NULL,
`title` varchar(100) DEFAULT NULL,
`date_creation` timestamp NOT NULL DEFAULT current_timestamp(),
`fk_user` int(10) unsigned NOT NULL,
`fk_entite` int(10) unsigned DEFAULT NULL,
`statut` enum('active','archive') NOT NULL DEFAULT 'active',
`description` text DEFAULT NULL,
`reply_permission` enum('all','admins_only','sender_only','none') NOT NULL DEFAULT 'all',
`is_pinned` tinyint(1) unsigned NOT NULL DEFAULT 0,
`expiry_date` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `idx_user` (`fk_user`),
KEY `idx_entite` (`fk_entite`),
KEY `idx_type` (`type`),
KEY `idx_statut` (`statut`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `email_counter` (
`id` int(10) unsigned NOT NULL DEFAULT 1,
`hour_start` timestamp NULL DEFAULT NULL,
`count` int(10) unsigned DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `email_queue` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
`to_email` varchar(255) DEFAULT NULL,
`subject` varchar(255) DEFAULT NULL,
`body` text DEFAULT NULL,
`headers` text DEFAULT NULL,
`created_at` timestamp NULL DEFAULT current_timestamp(),
`status` enum('pending','sent','failed') DEFAULT 'pending',
`attempts` int(10) unsigned DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `entites` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`encrypted_name` varchar(255) DEFAULT NULL,
`adresse1` varchar(45) DEFAULT '',
`adresse2` varchar(45) DEFAULT '',
`code_postal` varchar(5) DEFAULT '',
`ville` varchar(45) DEFAULT '',
`fk_region` int(10) unsigned DEFAULT NULL,
`fk_type` int(10) unsigned DEFAULT 1,
`encrypted_phone` varchar(128) DEFAULT '',
`encrypted_mobile` varchar(128) DEFAULT '',
`encrypted_email` varchar(255) DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`chk_stripe` tinyint(1) unsigned DEFAULT 0,
`encrypted_stripe_id` varchar(255) DEFAULT '',
`encrypted_iban` varchar(255) DEFAULT '',
`encrypted_bic` varchar(128) DEFAULT '',
`chk_demo` tinyint(1) unsigned DEFAULT 1,
`chk_mdp_manuel` tinyint(1) unsigned NOT NULL DEFAULT 1 COMMENT 'Gestion des mots de passe manuelle O/N',
`chk_copie_mail_recu` tinyint(1) unsigned NOT NULL DEFAULT 0,
`chk_accept_sms` tinyint(1) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
KEY `entites_ibfk_1` (`fk_region`),
KEY `entites_ibfk_2` (`fk_type`),
CONSTRAINT `entites_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE,
CONSTRAINT `entites_ibfk_2` FOREIGN KEY (`fk_type`) REFERENCES `x_entites_types` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1230 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `medias` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`support` varchar(45) NOT NULL DEFAULT '' COMMENT 'Type de support (entite, user, operation, passage)',
`support_id` int(10) unsigned NOT NULL DEFAULT 0 COMMENT 'ID de élément associé',
`fichier` varchar(250) NOT NULL DEFAULT '' COMMENT 'Nom du fichier stocké',
`file_type` varchar(50) DEFAULT NULL COMMENT 'Extension du fichier (pdf, jpg, xlsx, etc.)',
`file_category` varchar(50) DEFAULT NULL COMMENT 'export, logo, carte, etc.',
`file_size` int(10) unsigned DEFAULT NULL COMMENT 'Taille du fichier en octets',
`mime_type` varchar(100) DEFAULT NULL COMMENT 'Type MIME du fichier',
`original_name` varchar(255) DEFAULT NULL COMMENT 'Nom original du fichier uploadé',
`fk_entite` int(10) unsigned DEFAULT NULL COMMENT 'ID de entité propriétaire',
`fk_operation` int(10) unsigned DEFAULT NULL COMMENT 'ID de opération (pour passages)',
`file_path` varchar(500) DEFAULT NULL COMMENT 'Chemin complet du fichier',
`original_width` int(10) unsigned DEFAULT NULL COMMENT 'Largeur originale de image',
`original_height` int(10) unsigned DEFAULT NULL COMMENT 'Hauteur originale de image',
`processed_width` int(10) unsigned DEFAULT NULL COMMENT 'Largeur après traitement',
`processed_height` int(10) unsigned DEFAULT NULL COMMENT 'Hauteur après traitement',
`is_processed` tinyint(1) unsigned DEFAULT 0 COMMENT 'Image redimensionnée (1) ou originale (0)',
`description` varchar(100) NOT NULL DEFAULT '' COMMENT 'Description du fichier',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `idx_entite` (`fk_entite`),
KEY `idx_operation` (`fk_operation`),
KEY `idx_support_type` (`support`, `support_id`),
KEY `idx_file_type` (`file_type`),
KEY `idx_file_category` (`file_category`),
CONSTRAINT `fk_medias_entite` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT `fk_medias_operation` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `ope_pass` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`fk_adresse` varchar(25) DEFAULT '' COMMENT 'adresses.cp??.id',
`passed_at` timestamp NULL DEFAULT NULL COMMENT 'Date du passage',
`fk_type` int(10) unsigned DEFAULT 0,
`numero` varchar(10) NOT NULL DEFAULT '',
`rue` varchar(75) NOT NULL DEFAULT '',
`rue_bis` varchar(1) NOT NULL DEFAULT '',
`ville` varchar(75) NOT NULL DEFAULT '',
`fk_habitat` int(10) unsigned DEFAULT 1,
`appt` varchar(5) DEFAULT '',
`niveau` varchar(5) DEFAULT '',
`residence` varchar(75) DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`encrypted_name` varchar(255) NOT NULL DEFAULT '',
`montant` decimal(7,2) NOT NULL DEFAULT 0.00,
`fk_type_reglement` int(10) unsigned DEFAULT 1,
`remarque` text DEFAULT '',
`encrypted_email` varchar(255) DEFAULT '',
`nom_recu` varchar(50) DEFAULT NULL,
`date_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de réception',
`date_creat_recu` timestamp NULL DEFAULT NULL COMMENT 'Date de création du reçu',
`date_sent_recu` timestamp NULL DEFAULT NULL COMMENT 'Date envoi du reçu',
`email_erreur` varchar(30) DEFAULT '',
`chk_email_sent` tinyint(1) unsigned NOT NULL DEFAULT 0,
`encrypted_phone` varchar(128) NOT NULL DEFAULT '',
`is_striped` tinyint(1) unsigned NOT NULL DEFAULT 0,
`docremis` tinyint(1) unsigned DEFAULT 0,
`date_repasser` timestamp NULL DEFAULT NULL COMMENT 'Date prévue pour repasser',
`nb_passages` int(11) DEFAULT 1 COMMENT 'Nb passages pour les a repasser',
`chk_gps_maj` tinyint(1) unsigned DEFAULT 0,
`chk_map_create` tinyint(1) unsigned DEFAULT 0,
`chk_mobile` tinyint(1) unsigned DEFAULT 0,
`chk_synchro` tinyint(1) unsigned DEFAULT 1 COMMENT 'chk synchro entre web et appli',
`chk_api_adresse` tinyint(1) unsigned DEFAULT 0,
`chk_maj_adresse` tinyint(1) unsigned DEFAULT 0,
`anomalie` tinyint(1) unsigned DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_operation` (`fk_operation`),
KEY `fk_sector` (`fk_sector`),
KEY `fk_user` (`fk_user`),
KEY `fk_type` (`fk_type`),
KEY `fk_type_reglement` (`fk_type_reglement`),
KEY `email` (`encrypted_email`),
CONSTRAINT `ope_pass_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_2` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_3` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_pass_ibfk_4` FOREIGN KEY (`fk_type_reglement`) REFERENCES `x_types_reglements` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=19499566 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_pass_histo` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pass` int(10) unsigned NOT NULL DEFAULT 0,
`date_histo` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date historique',
`sujet` varchar(50) DEFAULT NULL,
`remarque` varchar(250) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `ope_pass_histo_fk_pass_IDX` (`fk_pass`) USING BTREE,
KEY `ope_pass_histo_date_histo_IDX` (`date_histo`) USING BTREE,
CONSTRAINT `ope_pass_histo_ibfk_1` FOREIGN KEY (`fk_pass`) REFERENCES `ope_pass` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=6752 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_sectors` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_old_sector` int(10) unsigned DEFAULT NULL,
`libelle` varchar(75) NOT NULL DEFAULT '',
`sector` text NOT NULL DEFAULT '',
`color` varchar(7) NOT NULL DEFAULT '#4B77BE',
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `fk_operation` (`fk_operation`),
CONSTRAINT `ope_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=27675 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `ope_users_ibfk_1` (`fk_operation`),
KEY `ope_users_ibfk_2` (`fk_user`),
CONSTRAINT `ope_users_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=199006 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `ope_users_sectors` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_operation` int(10) unsigned NOT NULL DEFAULT 0,
`fk_user` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
KEY `fk_operation` (`fk_operation`),
KEY `fk_user` (`fk_user`),
KEY `fk_sector` (`fk_sector`),
CONSTRAINT `ope_users_sectors_ibfk_1` FOREIGN KEY (`fk_operation`) REFERENCES `operations` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_sectors_ibfk_2` FOREIGN KEY (`fk_user`) REFERENCES `users` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ope_users_sectors_ibfk_3` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=48082 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `operations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned NOT NULL DEFAULT 1,
`libelle` varchar(75) NOT NULL DEFAULT '',
`date_deb` date NOT NULL DEFAULT '0000-00-00',
`date_fin` date NOT NULL DEFAULT '0000-00-00',
`chk_distinct_sectors` tinyint(1) unsigned NOT NULL DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned NOT NULL DEFAULT 0,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned NOT NULL DEFAULT 0,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_entite` (`fk_entite`),
KEY `date_deb` (`date_deb`),
CONSTRAINT `operations_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3121 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `params` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(35) NOT NULL DEFAULT '',
`valeur` varchar(255) NOT NULL DEFAULT '',
`aide` varchar(150) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `sectors_adresses` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_adresse` varchar(25) DEFAULT NULL COMMENT 'adresses.cp??.id',
`osm_id` int(10) unsigned NOT NULL DEFAULT 0,
`fk_sector` int(10) unsigned NOT NULL DEFAULT 0,
`osm_name` varchar(50) NOT NULL DEFAULT '',
`numero` varchar(5) NOT NULL DEFAULT '',
`rue_bis` varchar(5) NOT NULL DEFAULT '',
`rue` varchar(60) NOT NULL DEFAULT '',
`cp` varchar(5) NOT NULL DEFAULT '',
`ville` varchar(60) NOT NULL DEFAULT '',
`gps_lat` varchar(20) NOT NULL DEFAULT '',
`gps_lng` varchar(20) NOT NULL DEFAULT '',
`osm_date_creat` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
PRIMARY KEY (`id`),
KEY `sectors_adresses_fk_sector_index` (`fk_sector`),
KEY `sectors_adresses_numero_index` (`numero`),
KEY `sectors_adresses_rue_index` (`rue`),
KEY `sectors_adresses_ville_index` (`ville`),
CONSTRAINT `sectors_adresses_ibfk_1` FOREIGN KEY (`fk_sector`) REFERENCES `ope_sectors` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1562946 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_entite` int(10) unsigned DEFAULT 1,
`fk_role` int(10) unsigned DEFAULT 1,
`fk_titre` int(10) unsigned DEFAULT 1,
`encrypted_name` varchar(255) DEFAULT NULL,
`first_name` varchar(45) DEFAULT NULL,
`sect_name` varchar(60) DEFAULT '',
`encrypted_user_name` varchar(128) DEFAULT '',
`user_pass_hash` varchar(60) DEFAULT NULL,
`encrypted_phone` varchar(128) DEFAULT NULL,
`encrypted_mobile` varchar(128) DEFAULT NULL,
`encrypted_email` varchar(255) DEFAULT '',
`chk_alert_email` tinyint(1) unsigned DEFAULT 1,
`chk_suivi` tinyint(1) unsigned DEFAULT 0,
`date_naissance` date DEFAULT NULL,
`date_embauche` date DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp() COMMENT 'Date de création',
`fk_user_creat` int(10) unsigned DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp() COMMENT 'Date de modification',
`fk_user_modif` int(10) unsigned DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
KEY `fk_entite` (`fk_entite`),
KEY `username` (`encrypted_user_name`),
KEY `users_ibfk_2` (`fk_role`),
KEY `users_ibfk_3` (`fk_titre`),
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`fk_entite`) REFERENCES `entites` (`id`) ON UPDATE CASCADE,
CONSTRAINT `users_ibfk_2` FOREIGN KEY (`fk_role`) REFERENCES `x_users_roles` (`id`) ON UPDATE CASCADE,
CONSTRAINT `users_ibfk_3` FOREIGN KEY (`fk_titre`) REFERENCES `x_users_titres` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=10027748 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_departements` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`fk_region` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_departements_ibfk_1` (`fk_region`),
CONSTRAINT `x_departements_ibfk_1` FOREIGN KEY (`fk_region`) REFERENCES `x_regions` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=105 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_devises` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`symbole` varchar(6) DEFAULT NULL,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_entites_types` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_pays` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`code` varchar(3) DEFAULT NULL,
`fk_continent` int(10) unsigned DEFAULT NULL,
`fk_devise` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_pays_ibfk_1` (`fk_devise`),
CONSTRAINT `x_pays_ibfk_1` FOREIGN KEY (`fk_devise`) REFERENCES `x_devises` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table des pays avec leurs codes' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_regions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_pays` int(10) unsigned DEFAULT 1,
`libelle` varchar(45) DEFAULT NULL,
`libelle_long` varchar(45) DEFAULT NULL,
`table_osm` varchar(45) DEFAULT NULL,
`departements` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_regions_ibfk_1` (`fk_pays`),
CONSTRAINT `x_regions_ibfk_1` FOREIGN KEY (`fk_pays`) REFERENCES `x_pays` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_types_passages` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(10) DEFAULT NULL,
`color_button` varchar(15) DEFAULT NULL,
`color_mark` varchar(15) DEFAULT NULL,
`color_table` varchar(15) DEFAULT NULL,
`chk_active` tinyint(1) unsigned NOT NULL DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_types_reglements` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_users_roles` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents rôles des utilisateurs' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_users_titres` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`libelle` varchar(45) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Les différents titres des utilisateurs' `PAGE_COMPRESSED`='ON';
CREATE TABLE `x_villes` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`fk_departement` int(10) unsigned DEFAULT 1,
`libelle` varchar(65) DEFAULT NULL,
`code_postal` varchar(5) DEFAULT NULL,
`code_insee` varchar(5) DEFAULT NULL,
`chk_active` tinyint(1) unsigned DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `x_villes_ibfk_1` (`fk_departement`),
CONSTRAINT `x_villes_ibfk_1` FOREIGN KEY (`fk_departement`) REFERENCES `x_departements` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=38950 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE TABLE `z_sessions` (
`sid` text NOT NULL,
`fk_user` int(11) NOT NULL,
`role` varchar(10) DEFAULT NULL,
`date_modified` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`ip` varchar(50) NOT NULL,
`browser` varchar(150) NOT NULL,
`data` mediumtext DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `PAGE_COMPRESSED`='ON';
CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `chat_conversations_unread` AS select `r`.`id` AS `id`,`r`.`type` AS `type`,`r`.`title` AS `title`,`r`.`date_creation` AS `date_creation`,`r`.`fk_user` AS `fk_user`,`r`.`fk_entite` AS `fk_entite`,`r`.`statut` AS `statut`,`r`.`description` AS `description`,`r`.`reply_permission` AS `reply_permission`,`r`.`is_pinned` AS `is_pinned`,`r`.`expiry_date` AS `expiry_date`,`r`.`updated_at` AS `updated_at`,count(distinct `m`.`id`) AS `total_messages`,count(distinct `rm`.`id`) AS `read_messages`,count(distinct `m`.`id`) - count(distinct `rm`.`id`) AS `unread_messages`,(select `geo_app`.`chat_messages`.`date_sent` from `chat_messages` where `geo_app`.`chat_messages`.`fk_room` = `r`.`id` order by `geo_app`.`chat_messages`.`date_sent` desc limit 1) AS `last_message_date` from ((`chat_rooms` `r` left join `chat_messages` `m` on(`r`.`id` = `m`.`fk_room`)) left join `chat_read_messages` `rm` on(`m`.`id` = `rm`.`fk_message`)) group by `r`.`id`;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff