# Sync F → ERPNext — état au 2026-06-11 (session nocturne) > **TL;DR** — Moteur de sync delta **construit + testé en lecture seule** sur tes vraies données. > **AUCUNE écriture** n'a été faite (ni F, ni ERPNext). Aucun cron ajouté. Les déploiements > sont **inertes** (nouveau module `lib/legacy-sync.js` + routes gated, qui ne tournent que sur appel). > Décision attendue de toi avant toute écriture (voir §6). ## 1. Méthode de sync retenue (rappel) - **Pas de proxy live** vers F (prod fragile). **Pas de réplication binlog** (F n'a pas `log-bin` → exigerait un redémarrage du MySQL de facturation). - **Snapshot/delta pull** : le script existant `refresh-report-tables.sh` fait déjà un `mysqldump --single-transaction` (non bloquant, 1 connexion) des tables `account/delivery/service/product` → miroir local `legacy-db`. On lit TOUJOURS le miroir, jamais F live. - **Détection de changement** confirmée : `account.date_last` est une **vraie date de modification** (86 % des comptes, bouge en temps réel) → watermark possible sans rien changer dans F. `service`/`delivery` = INSERT par `id` (pas de date de modif). - **Écritures** ERPNext→F = via le **pont PHP** (`ops_reassign.php`, déjà utilisé pour le dispatch), idempotent par `legacy_account_id`. F jamais écrit en double. - **Propriété par domaine** : F = facturation (tarif/facture/statut billing), ERPNext = ops (dispatch/RDV/notes). Aucun champ disputé. ## 2. Ce qui a été construit - **`services/targo-hub/lib/legacy-sync.js`** (déployé, inerte) : - `previewCustomers()` — lit le miroir + ERPNext, classe chaque écart. - `runPreview()` — orchestre customers + locations + services, écrit le rapport complet dans `data/legacy-sync-preview.json`. - `applySafeAdds({confirm})` — **dry-run par défaut**, n'écrit QUE si `confirm==='SAFE-ADD'` (non exécuté). - **Routes** (gated par le token hub, donc OPS-only) : - `GET /legacy-sync/preview` → recalcule + renvoie le rapport. - `GET /legacy-sync/report` → relit le dernier rapport. - `POST /legacy-sync/apply-safe-adds` → **dry-run** sauf `{ "confirm": "SAFE-ADD" }`. ## 3. Politique de sécurité (le cœur de « preview d'abord ») Chaque écart de champ est classé : - **ADD** — ERPNext vide + F a une valeur → **sûr** (on remplit). - **CHANGE** — les deux ont une valeur différente → **révision humaine** (jamais auto-appliqué). - **NEVER-CLOBBER** — F vide + ERPNext a une valeur → **jamais écrasé** (protège ex. les `stripe_id`). - **`disabled`** — ERPNext le DÉRIVE des abonnements actifs (≠ `account.status`) → **jamais synchronisé**, juste rapporté comme « divergence de statut ». ## 4. Résultats du preview (réels, 2026-06-11) **Customers** — F 15 771 / ERPNext 15 303 : | Bucket | Nombre | Action | |---|---|---| | À créer (nouveaux comptes F) | **471** | nouveaux clients depuis la migration (id 15 429+) | | safe_add (enrichir champs vides) | **8 119** | **sûr** — email 7 073, tél 8 786, cell 2 310, stripe 138 | | needs_review (valeurs divergentes) | **921** | is_commercial 640, is_bad_payer 161, ppa 47, email 34, nom 21, langue 9 | | inchangés | 6 260 | — | | never-clobber (protégés) | 2 | 1 stripe_id + 1 cell (F vide) | **Divergence de statut** (NON auto-synchronisée) : 713 actifs-F/désactivés-ERP + **3 502 inactifs-F/actifs-ERP** (probables churns qu'ERPNext montre encore actifs → à réviser). **Locations** delivery→Service Location : 537 à créer (sur 17 645). **Services ACTIFS** (status=1) service→Service Subscription : **3 580 à créer** (F 41 685 / ERPNext 39 628). *(Avec l'historique inactif c'était 30 469 — désormais filtré sur `status=1`.)* ### Écran de revue OPS (déployé) Page **`/sync-legacy`** (« Sync F↔ERPNext » dans le menu, `requires: view_settings`) — `src/pages/LegacySyncPage.vue`. **Lecture seule** : appelle `GET /legacy-sync/preview` et affiche les buckets (à créer / à enrichir / à réviser), les ventilations par champ (ADD/CHANGE/never-clobber), la divergence de statut, et des échantillons. Aucun bouton d'écriture (l'apply reste CLI/gated tant que tu n'as pas donné le feu vert). ## 5. Comment l'utiliser ```bash # Preview (lecture seule) : docker exec targo-hub node -e "require('/app/lib/legacy-sync').runPreview().then(r=>console.log(JSON.stringify(r.customers.summary)))" # Rapport complet : cat /opt/targo-hub/data/legacy-sync-preview.json # (dans le conteneur : /app/data/…) # Dry-run de l'apply (n'écrit RIEN) : docker exec targo-hub node -e "require('/app/lib/legacy-sync').applySafeAdds({}).then(r=>console.log(JSON.stringify(r)))" ``` ## 6. Décisions / prochaines étapes (À TON RÉVEIL) 1. **Feu vert pour `safe_add` ?** Remplir les 8 119 champs vides (email/tél) est sûr et réversible (PUT partiel). → lancer `applySafeAdds({confirm:'SAFE-ADD'})`. (Nuance : email/tél canoniques vivent sur le doctype **Contact** ; on remplit ici la copie `Customer.email_id`/`tel_home` pour recherche/affichage — le sync Contact complet est une étape ultérieure.) 2. **needs_review (921)** : surtout `is_commercial` (640) — vérifier le mapping (F.`commercial` vs ERPNext `is_commercial`) ; petit volume, révisable. 3. **Divergence de statut (3 502 inactifs-F/actifs-ERP)** : décider la règle (réconcilier ERPNext.disabled depuis F.status pour les terminés ?). C'est un signal de churn, pas un bug. 4. **Services** : filtrer `status=1` + mapper service→Service Subscription (+Subscription) proprement. 5. **Fraîcheur F→miroir** : planifier le delta (watermark `date_last`/`id`) — toujours pas de cron ajouté (en attente de ta décision de cadence). 6. **Suite du funnel** : Lead (doctype ERPNext) + wizard rep, puis push-to-F (#65), puis onboarding (flow), puis RDV (booking déjà construit). ## 7. Garanties de cette session - ✅ Zéro écriture dans F. ✅ Zéro écriture dans ERPNext. ✅ Aucun cron/scheduler ajouté. - ✅ Scheduler ERPNext reste en pause ; facturation F reste autoritaire. - ✅ Déploiements = nouveau module + routes gated (n'altèrent aucun comportement existant).