Accumulated work on the dispatch/legacy-writeback branch: - Communications UI: CommunicationsPage, ConversationFullPage, DepartmentBoard, PipelineBoard, ReaderStack, Orchestrator/NewTicket/ServiceStatus/Outbox dialogs; hub gmail.js, ticket-collab.js, outbox.js, coupon-triage.js, client-diag.js. - Billing/sync mirror (F→ERPNext): legacy-payments.js, legacy-sync.js, sync-orchestrator.js, supplier-invoices.js, municipality.js + incremental migration scripts; LegacySyncPage, SupplierInvoices + negative-billing / terminated-active reports. - Roster/campaigns/network/voice: roster + roster-assistant, campaigns, giftbit, olt-snmp, traccar, twilio, vision, tech-absence-sms, ai/agent/config/helpers, legacy-dispatch-sync; ops PlanificationPage, RapportsPage, Settings, Tickets, ClientDetail updates. - docs/ PLATFORM_GUIDE + UI_AND_OPTIMIZATION; .gitignore __pycache__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
72 lines
6.0 KiB
Markdown
72 lines
6.0 KiB
Markdown
# 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).
|