gigafibre-fsm/scripts/targo-sync/README.md
louispaulb 8a51925da8 devices: import du manquant + fix liens SS.device orphelins + catch-up quotidien
- import_devices_and_enrich.py : creds via env (secret hors repo) + SKIP_ENRICH (devices seuls).
  Rattrapage : 1160 devices importés (tous les devices F courants sont maintenant en Service Equipment).
- FIX bug d'import : rename_all_doctypes.py avait renommé Service Equipment (EQ-{md5}→EQP-{seq})
  sans cascader vers Service Subscription.device → 6980 liens orphelins ("Could not find Device: EQ-…",
  bloquait les saves pleine-doc). 5241 remappés via legacy_device_id+md5 (UPDATE PG direct).
  Résiduel 1739 = device supprimé côté F (1584 Actif / 155 Annulé) → décision NULL vs relink en attente.
- Récurrence : tickets-daily.sh importe désormais AUSSI les devices (SKIP_ENRICH=1), quotidien 04:30.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 23:09:16 -04:00

5.2 KiB

targo-sync — miroir opérationnel F (legacy) → ERPNext

Snapshot versionné des scripts qui tournent sur l'hôte de prod dans /opt/targo-sync/ (hors conteneur). Avant 2026-07-06 ils n'étaient dans aucun repo — c'est le vrai moteur de synchronisation « legacy F → ERPNext » pour la couche opérationnelle (ce que les équipes voient : clients, adresses, services/abonnements, factures, paiements). F reste autoritaire pour la facturation (scheduler ERPNext en pause) ; ERPNext = miroir + grand livre à la bascule.

Cadence

Cron horaire sur l'hôte (15 * * * *) → run.sh → 4 étapes, dans l'ordre du DAG de dépendances :

  1. Comptes manquants — hub POST /legacy-payments/ensure-customers (crée les Customer absents).
  2. Adresses + servicessync_services_incremental.py (Python, exécuté dans erpnext-backend-1).
  3. Facturessync_invoices_incremental.py (idem).
  4. Paiements + soldes — hub POST /legacy-payments/sync-cycle.

Les scripts Python sont idempotents (ON CONFLICT DO NOTHING pour les créations, UPDATE conditionnel pour les rafraîchissements). APPLY=0 = dry-run (n'écrit rien) ; run.sh lance en APPLY=1. Autres env : PG_HOST (défaut db), LIMIT (taille de lot), LEGACY_HOST.

Déploiement (host, hors repo)

scp scripts/targo-sync/*.py scripts/targo-sync/run.sh root@<prod>:/opt/targo-sync/
# le cron docker cp le .py dans erpnext-backend-1 puis l'exécute — pas de restart requis

Dry-run manuel d'un script avant bascule :

docker cp /opt/targo-sync/sync_services_incremental.py erpnext-backend-1:/tmp/svc.py
docker exec -e APPLY=0 -e PG_HOST=db erpnext-backend-1 \
  /home/frappe/frappe-bench/env/bin/python /tmp/svc.py

⚠️ FIX 2026-07-06 — abonnements fantômes (compte résilié affiché « Actif »)

En F, résilier un compte met account.status ∈ {3,4,5} mais laisse les services à service.status=1 (F ne cascade pas). svc_status() dérivait le statut du seul service.status → un compte résilié ressortait « Actif » dans ERPNext. Comme le statut client est dérivé des abonnements, ~2 663 ex-clients apparaissaient actifs et ~3 855 abonnements étaient des fantômes (MRR gonflé, audiences polluées).

Correctif dans sync_services_incremental.py (marqueur FIX 2026-07-06) : svc_status() charge une fois SELECT id FROM account WHERE status IN (3,4,5) et force Annulé dès que le compte est résilié, sur les deux chemins (Phase B création + Phase D rafraîchissement). Sans ce garde-fou, le cron ré-activait les fantômes à chaque heure. Voir la mémoire projet feedback_ghost_active_subscriptions.

Réconciliation visible dans OPS : page /sync-legacy → carte « Réconciliation par module » (endpoint hub GET /legacy-sync/scoreboard, métrique ghost_active_subscriptions = doit rester 0).

Fichiers

Catch-up QUOTIDIEN — tickets + appareils (cron séparé)

tickets-daily.sh (cron 30 4 * * *) lance deux imports idempotents (skip par legacy_*_id, n'importent que les nouveaux) :

  • migrate_tickets.pytabIssue (skip par legacy_ticket_id). Messages des tickets closed non importés (deferred ; import_ticket_msgs.py au besoin).
  • import_devices_and_enrich.py (avec SKIP_ENRICH=1 = devices seulement) → tabService Equipment (skip par legacy_device_id). Sans SKIP_ENRICH, enrichit aussi les Service Location (fibre/OLT/VLAN).

Séparé de run.sh (horaire) car les scans complets (252k tickets / 11k devices) sont trop lourds pour l'horaire. Canoniques dans scripts/migration/ ; en déployer une copie dans /opt/targo-sync/.

⚠️ Bug d'import historique corrigé 2026-07-06 : rename_all_doctypes.py a renommé les Service Equipment (EQ-{md5}EQP-{seq}) SANS mettre à jour Service Subscription.device → 6980 liens orphelins (Could not find Device: EQ-…, bloquait les saves pleine-doc Frappe). 5241 remappés (via legacy_device_id+md5) ; 1739 résiduels pointaient un device supprimé côté F (à trancher : NULL vs relink au device courant du lieu). Voir mémoire feedback_device_hierarchy.

🔐 Secrets — JAMAIS dans le repo

Le mot de passe F (LEGACY_PW) se charge depuis /opt/targo-sync/secrets.env (hôte, chmod 600, non versionné — voir .gitignore). Les .py du repo ont un défaut VIDE (os.environ.get("LEGACY_PW","")). Ne jamais committer une copie prod d'un script qui aurait le mot de passe en dur.

Fichiers

Fichier Rôle
run.sh Orchestrateur cron horaire (4 étapes DAG)
sync_services_incremental.py Service Location (delivery) + Service Subscription (service) : création + prix + statut (fix fantômes raffiné)
sync_invoices_incremental.py Sales Invoice (miroir des factures F ; statut reflétant F)
tickets-daily.sh Wrapper cron quotidien → migrate_tickets.py + import_devices_and_enrich.py (secret via secrets.env)
migrate_tickets.py (déployé depuis scripts/migration/) tickets F → Issue
import_devices_and_enrich.py (déployé depuis scripts/migration/) devices F → Service Equipment
secrets.env Hôte seulement, non versionné : LEGACY_PW=…