⚠️ SÉCURITÉ : le défaut en dur du mdp F ("facturation") avait fuité dans sync_services / sync_invoices (commit004c3f8). Défaut passé à "" (secret via env uniquement). Le mdp est DÉSORMAIS chargé depuis /opt/targo-sync/secrets.env (hôte, chmod 600, gitignored). NB : le mdp reste dans l'HISTORIQUE git (004c3f8) → rotation recommandée (décision Louis). Cron quotidien tickets (récurrence #26) : - tickets-daily.sh (30 4 * * *) → migrate_tickets.py (idempotent), n'importe que les nouveaux. - Séparé de run.sh (horaire) : scan 252k trop lourd pour l'horaire. - .gitignore : secrets.env / *.env / *.log / .lock jamais versionnés. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
78 lines
4.2 KiB
Markdown
78 lines
4.2 KiB
Markdown
# 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 + services** — `sync_services_incremental.py` (Python, exécuté **dans** `erpnext-backend-1`).
|
|
3. **Factures** — `sync_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)
|
|
|
|
```sh
|
|
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 :
|
|
|
|
```sh
|
|
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
|
|
|
|
## Tickets → Issue (cron QUOTIDIEN séparé)
|
|
|
|
`tickets-daily.sh` (cron `30 4 * * *`) lance `migrate_tickets.py` (idempotent, skip par
|
|
`legacy_ticket_id`) pour n'importer que les nouveaux tickets. Séparé de `run.sh` (horaire) car
|
|
le scan de 252k tickets est trop lourd pour l'horaire. Le canonique de `migrate_tickets.py` est
|
|
`scripts/migration/` ; en déployer une copie dans `/opt/targo-sync/`. Messages des tickets *closed*
|
|
non importés (deferred ; `import_ticket_msgs.py` au besoin).
|
|
|
|
## 🔐 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` (secret via `secrets.env`) |
|
|
| `secrets.env` | **Hôte seulement, non versionné** : `LEGACY_PW=…` |
|