# Audit des données du système actuel — ALDANA / Kinetic Silence

> **Jalon A.1 du plan de mise en production**
> **À compléter avant 2026-05-27 (fin S1)**
> Document de travail pour cartographier les données du système actuel vers le nouveau schéma PHP/MySQL.

---

## Méthode

1. Identifier le système source utilisé par le studio (cf [docs/questionnaire-client.md](questionnaire-client.md) §3)
2. Récupérer un **échantillon anonymisé** (5-10 lignes) de chaque type de donnée
3. Cartographier colonne par colonne ancien → nouveau (tableaux ci-dessous)
4. Identifier les écarts (champs manquants, formats incompatibles, valeurs incohérentes)
5. Définir les règles de transformation et les valeurs par défaut
6. Valider le mapping avec le studio

---

## 1. Système source

**Système identifié** : _______________________ (cocher dans le questionnaire client §3.1)

**Méthode d'export disponible** :
- [ ] Export CSV manuel depuis l'interface
- [ ] Export Excel
- [ ] API
- [ ] Dump SQL fourni par le support
- [ ] Ressaisie manuelle nécessaire (dernier recours)

**Fichiers d'échantillon reçus** :
- `samples/clients-sample.csv` — _____ lignes
- `samples/subscriptions-sample.csv` — _____ lignes
- `samples/bookings-sample.csv` — _____ lignes
- `samples/payments-sample.csv` — _____ lignes

**Stockage** : Les fichiers sont **anonymisés** et stockés dans `storage/imports/samples/` (jamais commité git).

---

## 2. Mapping CLIENTS — `t_users`

### Schéma cible

| Colonne `t_users` | Type | Nullable | Défaut |
|---|---|---|---|
| `user_ID` | INT UNSIGNED AUTO_INCREMENT | NO | — |
| `legacy_id` | VARCHAR(64) | YES | NULL — identifiant ancien système |
| `email` | VARCHAR(190) | NO | UNIQUE |
| `password_hash` | VARCHAR(255) | NO | (Argon2id généré, token reset envoyé) |
| `firstname` | VARCHAR(100) | NO | — |
| `lastname` | VARCHAR(100) | NO | — |
| `phone` | VARCHAR(20) | YES | NULL |
| `birthdate` | DATE | YES | NULL |
| `role_ID` | INT UNSIGNED | NO | 10 (Client) |
| `address` | VARCHAR(255) | YES | NULL |
| `postal_code` | VARCHAR(10) | YES | NULL |
| `city` | VARCHAR(100) | YES | NULL |
| `country` | VARCHAR(2) | YES | 'FR' |
| `newsletter_optin` | BOOLEAN | NO | FALSE (RGPD strict) |
| `created_at` | DATETIME | NO | NOW() |
| `imported_at` | DATETIME | YES | NULL (set par script import) |
| `last_login_at` | DATETIME | YES | NULL |
| `is_active` | BOOLEAN | NO | TRUE |

### Mapping ancien → nouveau

| Champ ancien système | Champ cible | Transformation | Validation |
|---|---|---|---|
| ex. `ID` | `legacy_id` | `(string)` | unique |
| ex. `Email` | `email` | `mb_strtolower(trim($v))` | regex email + unique |
| ex. `Nom complet` | `firstname` + `lastname` | `explode(' ', $v, 2)` | non vide |
| ex. `Téléphone` | `phone` | `preg_replace('/\D/', '', $v)` puis format `+33...` | optionnel |
| ex. `Date inscription` | `created_at` | `DateTime::createFromFormat('d/m/Y', $v)` | < NOW() |
| ex. `Newsletter` | `newsletter_optin` | `in_array(strtolower($v), ['oui','yes','1','true'])` | bool |
| | | | |

### Règles spéciales

- **Doublons email** : détecter avant insert. Si doublon, garder le plus récent + log dans `imports/log/doublons-clients.json`
- **Email manquant** : skip ligne + log
- **Mot de passe** : non importé (sécurité). Génération token reset à l'import + email "bienvenue migration" avec lien de définition du mot de passe
- **RGPD** : si `newsletter_optin` ambigu, défaut FALSE (consentement explicite requis)

---

## 3. Mapping FORFAITS / SUBSCRIPTIONS — `t_subscriptions`

### Schéma cible

| Colonne `t_subscriptions` | Type | Nullable | Défaut |
|---|---|---|---|
| `subscription_ID` | INT UNSIGNED AUTO_INCREMENT | NO | — |
| `legacy_id` | VARCHAR(64) | YES | NULL |
| `user_ID` | INT UNSIGNED | NO | FK `t_users` |
| `plan_ID` | INT UNSIGNED | NO | FK `t_subscription_plans` |
| `status` | ENUM | NO | `'active'|'expired'|'suspended'|'imported'|'refunded'` |
| `started_at` | DATE | NO | — |
| `expires_at` | DATE | YES | NULL (illimité = NULL) |
| `original_credits` | INT UNSIGNED | YES | NULL |
| `remaining_credits` | INT UNSIGNED | YES | NULL |
| `purchase_amount_ttc` | DECIMAL(10,2) | YES | NULL |
| `payment_method` | VARCHAR(20) | YES | 'imported' |
| `imported_at` | DATETIME | YES | NULL |
| `notes` | TEXT | YES | NULL |

### Mapping ancien → nouveau

| Champ ancien | Champ cible | Transformation | Validation |
|---|---|---|---|
| ex. `Client_Email` | `user_ID` | lookup `t_users.email` → `user_ID` | obligatoire |
| ex. `Type forfait` | `plan_ID` | mapping ci-dessous | obligatoire |
| ex. `Date début` | `started_at` | `DateTime::createFromFormat(...)` | < NOW() |
| ex. `Date fin` | `expires_at` | idem ou NULL si illimité | > started_at |
| ex. `Séances restantes` | `remaining_credits` | `(int)` | ≥ 0 |
| ex. `Statut` | `status` | mapping ci-dessous | enum valide |
| | | | |

### Mapping types de forfait

À remplir avec le studio (questionnaire §3.4) :

| Nom ancien | `plan_ID` cible | Nom dans `t_subscription_plans` |
|---|---|---|
| ex. `Carnet 10` | 1 | Carnet 10 séances |
| ex. `Illimité mensuel` | 2 | Illimité mensuel |
| ex. `Trimestre` | 3 | Trimestriel |
| | | |

### Mapping statuts

| Valeur ancienne | Valeur cible |
|---|---|
| `Actif` / `En cours` | `active` |
| `Expiré` / `Terminé` | `expired` |
| `Suspendu` / `Pause` | `suspended` |
| (import par défaut) | `imported` |
| `Remboursé` / `Annulé` | `refunded` |

### Règles spéciales

- **Forfait illimité expiré** : si `expires_at < NOW()` → forcer `status = 'expired'` quel que soit le statut ancien
- **Crédits négatifs** : impossible. Force à 0 + log
- **Forfait sans date début** : utiliser date d'import + log
- **Statut `imported`** vs `active` : différencier les vrais achats sur le nouveau site des migrations (utile pour comptabilité Jalon C)

---

## 4. Mapping HISTORIQUE RÉSERVATIONS — `t_bookings`

### Reprise optionnelle — recommandée 12 mois max

**Pourquoi limiter à 12 mois ?**
- Volume BDD raisonnable
- RGPD : minimisation des données
- Pertinence métier (au-delà, peu utile pour le studio)

### Schéma cible

| Colonne `t_bookings` | Type | Nullable | Défaut |
|---|---|---|---|
| `booking_ID` | INT UNSIGNED AUTO_INCREMENT | NO | — |
| `legacy_id` | VARCHAR(64) | YES | NULL |
| `user_ID` | INT UNSIGNED | NO | FK |
| `class_ID` | INT UNSIGNED | NO | FK ou NULL si cours ancien plus en BDD |
| `class_date` | DATE | NO | — (dénormalisé pour historique) |
| `status` | ENUM | NO | `'attended'|'cancelled'|'no_show'|'imported'` |
| `created_at` | DATETIME | NO | — |

### Difficulté principale

Les cours passés n'existent pas forcément dans `t_yoga_classes` (créés à la volée pour le futur). **Solution** : créer un cours "historique" générique par mois ou stocker `class_date` directement sans FK stricte.

**Décision à valider avec le studio** :
- [ ] Reprendre uniquement les réservations futures (recommandé — simple)
- [ ] Reprendre 12 mois d'historique pour stats client (~moyen)
- [ ] Reprendre toute l'histoire (déconseillé)

---

## 5. Mapping HISTORIQUE PAIEMENTS — `t_payments`

### Reprise OBLIGATOIRE pour la comptabilité

Article 102 B du LPF : conservation des factures **10 ans**.

### Schéma cible

| Colonne `t_payments` | Type | Nullable | Défaut |
|---|---|---|---|
| `payment_ID` | INT UNSIGNED AUTO_INCREMENT | NO | — |
| `legacy_id` | VARCHAR(64) | YES | NULL |
| `legacy_invoice_number` | VARCHAR(50) | YES | NULL — n° facture ancien système |
| `user_ID` | INT UNSIGNED | YES | FK (NULL si client supprimé RGPD) |
| `subscription_ID` | INT UNSIGNED | YES | FK |
| `amount_ttc` | DECIMAL(10,2) | NO | — |
| `amount_ht` | DECIMAL(10,2) | YES | NULL si franchise TVA |
| `vat_rate` | DECIMAL(5,2) | YES | NULL |
| `method` | VARCHAR(20) | NO | `card|sepa|cash|check|transfer|imported` |
| `status` | ENUM | NO | `'succeeded'|'refunded'|'imported'` |
| `paid_at` | DATETIME | NO | — |
| `invoice_number` | VARCHAR(50) | YES | NULL — nouveau n° si refacturé |
| `stripe_payment_intent_id` | VARCHAR(100) | YES | NULL |
| `imported_at` | DATETIME | YES | NULL |

### Règles critiques

- **Numérotation** : conserver le `legacy_invoice_number` tel quel pour traçabilité. Ne pas re-numéroter les paiements importés (la séquence `t_invoice_sequence` reprend uniquement pour les **nouvelles** factures à partir du lancement)
- **Mode de paiement chèque/espèces** : conserver comme `cash` / `check`, **pas** comme `card`
- **Statut** : `imported` distinct de `succeeded` pour exclure de certains rapports
- **Lien client cassé** : si client supprimé RGPD côté ancien système, `user_ID = NULL` + nom/prénom dénormalisés dans une colonne `customer_label` pour la compta

### Colonne supplémentaire à ajouter

`database/migrations/023_alter_payments_legacy.sql` :
```sql
ALTER TABLE t_payments
  ADD COLUMN legacy_id VARCHAR(64) NULL UNIQUE AFTER payment_ID,
  ADD COLUMN legacy_invoice_number VARCHAR(50) NULL AFTER legacy_id,
  ADD COLUMN customer_label VARCHAR(200) NULL AFTER user_ID,
  ADD COLUMN imported_at DATETIME NULL;
```

---

## 6. Mapping PROFESSEURS — `t_teachers` + `t_users`

### Cas particulier

Un professeur est à la fois :
- Un utilisateur (`t_users` avec `role_ID = 11`)
- Un profil public (`t_teachers` avec FK vers `user_ID`)

### Mapping

| Champ ancien | Champ cible | Notes |
|---|---|---|
| Nom prof | `t_users.firstname` + `t_users.lastname` | |
| Email prof | `t_users.email` | unique |
| Bio courte | `t_teachers.bio_short` | < 200 caractères |
| Bio longue | `t_teachers.bio_long` | TEXT |
| Photo | `t_teachers.photo_path` | upload manuel ou import depuis URL |
| Spécialités | `t_teachers.specialties` | JSON array : `["Hatha","Vinyasa"]` |
| Statut | `t_users.is_active` | bool |

### Règles

- Mot de passe : génération token reset à l'import (idem clients)
- Sans email → skip + log (impossible d'authentifier)

---

## 7. Mapping COURS PLANIFIÉS — `t_yoga_classes`

### Reprise des séances **futures uniquement**

À partir de la date de mise en prod.

### Schéma

| Champ ancien | Champ cible | Notes |
|---|---|---|
| Date séance | `class_date` | format ISO `YYYY-MM-DD` |
| Heure début | `start_time` | `HH:MM:SS` |
| Durée (min) | `duration_minutes` | |
| Salle | `room_ID` | lookup par nom |
| Pratique | `practice_ID` | lookup par nom |
| Prof | `teacher_ID` | lookup par email |
| Capacité max | `capacity_max` | |
| Places restantes | `spots_left` | `capacity_max - bookings_count` recalculé |
| Niveau | `level` | enum `tous|débutant|intermédiaire|avancé` |

### Difficulté

Reprendre uniquement les séances futures **avec les réservations associées** sinon le décompte `spots_left` est faux. Préférer une **bascule franche** : nouvelle plateforme = nouvelles séances créées manuellement par l'admin.

**Décision à valider** :
- [ ] Reprise des séances futures + réservations (complexe)
- [ ] Bascule franche : seules les séances créées dans la nouvelle plateforme comptent (simple — recommandé)

---

## 8. Données NON reprises

À documenter dans le mode d'emploi client final :

| Type donnée | Pourquoi non repris | Action client |
|---|---|---|
| Mots de passe clients | Sécurité (hash propriétaire ancien) | Email "définir mot de passe" à la migration |
| Historique > 12 mois | RGPD minimisation + volume | Archive PDF/CSV remise au client séparément |
| Notes internes profs | Pas de schéma équivalent | Migration manuelle au cas par cas |
| Photos profil clients | Volume + RGPD consentement | Réupload volontaire après reset |
| Données paiement Stripe | Tokens propriétaires ancien système | Nouveau paiement requis au prochain forfait |

---

## 9. Stratégie de bascule

### Option A — Bascule **propre** (recommandée)

1. Geler les nouvelles inscriptions sur l'ancien système 7 jours avant bascule
2. Export final des données J-1
3. Import dans la nouvelle BDD prod
4. Bascule DNS
5. Email de bienvenue migration envoyé par batch
6. Ancien système en lecture seule pendant 30 j (au cas où)
7. Archivage final + suppression ancien système

### Option B — Bascule **progressive** (déconseillée)

Faire cohabiter les deux systèmes pendant 1 mois. Risque : doubles réservations, confusion clients, double saisie admin.

---

## 10. Validation pré-import (procédure obligatoire)

Avant tout import réel :

1. **Backup BDD prod** complet (script `database/backups/dump-pre-import-YYYY-MM-DD.sql`)
2. **Dry-run** sur 100 % des données : `php database/imports/01_import_clients.php --dry-run`
3. Lecture du rapport `imports/log/dry-run-YYYY-MM-DD.json`
4. Vérification manuelle de 5 lignes témoins
5. Import réel en transaction `START TRANSACTION ... COMMIT`
6. Tests post-import :
   - Total clients importés == total échantillon - skips
   - Aucune email en double
   - 5 clients témoins peuvent se connecter via le lien token
7. **Validation client** via Go/No-go formel par email

---

## 11. Calendrier de l'audit

| Étape | Date cible | Responsable |
|---|---|---|
| Identification système source | 2026-05-23 | Studio |
| Fourniture échantillon anonymisé | 2026-05-25 | Studio |
| Cartographie colonnes (ce document) | 2026-05-27 | Prestataire |
| Validation mapping par studio | 2026-05-29 | Studio + prestataire |
| Écriture scripts import (Lot A.2) | 2026-06-02 | Prestataire |
| Dry-run sur échantillon | 2026-06-04 | Prestataire |
| Dry-run sur dataset complet | 2026-07-05 | Prestataire |
| **Import réel en prod** | 2026-07-10 | Prestataire (validation studio) |
| Email migration aux clients | 2026-07-11 | Prestataire (batch 50/h) |

---

## 12. Notes et observations

> Espace pour les remarques pendant l'audit

- _______________________________________
- _______________________________________
- _______________________________________

---

## Validation

**Audit réalisé par** : R. Galofaro
**Date de finalisation** : _______________________
**Mapping validé par le studio** (signature ou email) : _______________________
**Date validation** : _______________________
