From f33f7a630920a059dccb52da97cda66e7bc8200c Mon Sep 17 00:00:00 2001 From: louispaulb Date: Sun, 7 Jun 2026 01:28:54 -0400 Subject: [PATCH] Optimisation (consolidation helpers address) + doc Vision/modularisation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Optimisation sûre (vérifiée, 0 régression) : - helpers.js : `cors()` partagé (en-têtes CORS génériques) au lieu de 2 copies locales. - address-conformity.js : réutilise `pool` (address-db) + `cors` (helpers) au lieu de redéfinir un Pool + cors → 1 seul client pg local partagé pour rqa_addresses/fiber. - address-validate.js : utilise helpers.cors. docs/architecture/VISION.md (NOUVEAU) — vision + plan de modularisation + roadmap d'optimisation, fondé sur un audit chiffré (hub 58 modules/23k lignes, Ops 45k lignes, god-files identifiés). Découpe en 9 domaines (bounded contexts), principe « source de vérité + validation à la saisie + lien stable » (modèle Adresses généralisé à Client/Device/Service), optimisations P0/P1/P2, métriques de succès. Complète les docs architecture existants + ENGINEERING_PRACTICES. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/architecture/VISION.md | 129 +++++++++++++++++++ services/targo-hub/lib/address-conformity.js | 23 +--- services/targo-hub/lib/address-validate.js | 11 +- services/targo-hub/lib/helpers.js | 10 +- 4 files changed, 141 insertions(+), 32 deletions(-) create mode 100644 docs/architecture/VISION.md diff --git a/docs/architecture/VISION.md b/docs/architecture/VISION.md new file mode 100644 index 0000000..e535d76 --- /dev/null +++ b/docs/architecture/VISION.md @@ -0,0 +1,129 @@ +# Vision, modularisation & optimisation — Plateforme TARGO / Gigafibre + +> Document stratégique. Complète `overview.md`, `app-design.md`, `data-model.md`, `module-interactions.md` +> et `../ENGINEERING_PRACTICES.md`. Objectif : passer d'une croissance organique (god-files, modules plats) +> à une architecture **modulaire par domaine** avec des **sources de vérité** fiables — pour livrer vite ET sûr. + +## 0. Vision en une phrase +Un **hub d'orchestration léger** (Node) au-dessus d'**ERPNext** (ERP/source de données), organisé en +**domaines métier autonomes** (bounded contexts), chacun avec sa **source de vérité canonique**, sa +**validation à la saisie** et ses **liens stables** — pendant qu'on **éteint progressivement le legacy**. + +--- + +## 1. État actuel (audit chiffré, 2026-06-07) + +| Composant | Volume | Plus gros fichiers (god-files) | +|---|---|---| +| **targo-hub** (Node) | 58 modules, ~23 000 lignes, 52 routes (lazy-require) | campaigns 2366 · payments 1376 · network-intel 1221 · contracts 1091 · roster 1040 · tech-mobile 961 · dispatch 852 | +| **Ops SPA** (Vue3/Quasar) | ~45 000 lignes | ProjectWizard 2891 · DispatchPage 2162 · PlanificationPage 1863 · NetworkPage 1829 · ClientDetailPage 1715 | + +**Forces** : hub mono-process simple (routage `path.startsWith` + `require` paresseux), ERPNext comme socle +données, frappe_pg (PostgreSQL), intégrations riches (legacy MariaDB, Ministra, GenieACS, OLT SNMP, Stripe, +Mailjet, Authentik, Karrio), base d'adresses RQA **locale** (5,24 M) déjà branchée. + +**Dettes techniques** (priorisées plus bas) : +1. **God-files** : 5 fichiers front >1700 lignes, 2 back >1300 — mélangent UI/état/règles métier/I/O. +2. **Modules plats sans frontière de domaine** : 58 fichiers `lib/*.js` au même niveau ; couplage implicite. +3. **Helpers réimplémentés** : `norm()` (accents), `cors()`, `haversine()`, `stripHtml()` dupliqués dans + ~6 modules (address-*, legacy-dispatch-sync, roster, serviceability, tech-absence-sms). *(cors/pool : consolidés ✅)* +4. **Pas de tests** ni de **CI/CD** (déploiement = `scp` manuel + `docker restart`). +5. **Observabilité inégale** : `catch {}` muets par endroits ; heartbeat/réconciliation présents seulement + sur le pont legacy. `erp.create/update` ne *throw* pas → des erreurs ont déjà été comptées comme succès. + +--- + +## 2. Principe directeur : Domaines + Source de vérité + +Deux règles structurent toute la suite : + +**(A) Découper par DOMAINE (bounded context), pas par fichier.** Chaque domaine expose une interface +publique étroite (un `index` + des services) ; le reste est interne. Le routage du hub délègue au domaine. + +**(B) Chaque entité a UNE source de vérité + validation à la saisie + lien stable.** On ne re-cherche pas +avec un processus complexe à chaque fois : on **résout une fois**, on **persiste le lien canonique**, le +downstream **lit le lien**. Modèle de référence : les **adresses** (cf. `reference/` + page Conformité) — +`rqa_addresses` (RQA local) ← `aq_address_id`/`linked_address`/`address_validation_status` sur Service Location. + +--- + +## 3. Carte des domaines cible + +Regroupement des 58 modules hub + pages Ops en **9 domaines**. (Refactor par déplacement progressif, pas big-bang.) + +| Domaine | Hub (lib/…) | Ops (pages/modules) | Source de vérité | +|---|---|---|---| +| **Identité & Accès** | auth, (Authentik) | usePermissions, MainLayout | Authentik + Capabilities | +| **CRM / Clients** | store, **address-db/validate/conformity**, address-search | Clients, ClientDetail | Customer (ERPNext) · **Adresse = RQA** | +| **Dispatch & Terrain** | dispatch, roster, tech-mobile, legacy-dispatch-sync, (geo/routing Mapbox) | Dispatch, Planification, RendezVous, module tech | Dispatch Job · Shift/Roster | +| **Réseau & Infra** | network-intel, olt-snmp, devices, outage-monitor, oktopus, (genieacs) | Network | GenieACS (CPE) · OLT · device | +| **Facturation & Paiements** | payments, contracts, acceptance, store/checkout | Rapports, ContratBLB | **Legacy billing (autoritaire jusqu'au cutover)** → ERPNext | +| **Marketing & Campagnes** | campaigns, offers, ai (traduction) | module campaigns | Campaign · Gift (Giftbit) | +| **IA & Agent** | ai, agent, voice-agent, flow-runtime, conversation | Copilote, AgentFlows | Agent flows | +| **Intégrations & Legacy** | erp, legacy DB, Ministra, Karrio, Giftbit | — | Adaptateurs (anti-corruption layer) | +| **Plateforme** | helpers, config, observabilité | composables partagés, components/shared | — | + +**Cible d'arborescence hub** (itératif) : `lib//.js` + `lib//index.js` (interface +publique) ; `lib/util/` (norm, cors, geo, html) ; `server.js` route vers `require('./lib/')`. + +**Cible Ops** : `src/modules//` (pages + composants + composables + api du domaine), comme +`modules/campaigns` et `modules/tech` le font déjà — étendre ce pattern à dispatch, roster, clients, network. + +--- + +## 4. Optimisations priorisées + +### P0 — Hygiène (sûr, rapide, vérifiable au build) +- **Extraire les helpers partagés** dans `lib/util/` : `norm` (accents), `cors` ✅, `haversineKm`, `stripHtml`, + `tzDate`. Remplacer les ~6 réimplémentations. *(cors + pool address déjà consolidés.)* +- **Uniformiser le contrat handler** : `erp.create/update` renvoient `{ok}` → tout appelant DOIT vérifier + `r.ok` (déjà corrigé dans le pont ; auditer payments/contracts/store). +- **Supprimer le code mort** + `catch {}` muets → `log()`. +- **Boundary I/O** : un seul client `pg` partagé pour les lectures locales (address-db.pool, réutilisé ✅). + +### P1 — Décomposition des god-files (par domaine, sans changer le comportement) +- Front : `ProjectWizard` (2891), `DispatchPage` (2162), `PlanificationPage` (1863), `ClientDetailPage` (1715) + → extraire des **composables** (`use*`) + **sous-composants** (détail/sections). PlanificationPage : sortir + packedDay/route-planner, le panneau d'assignation, l'éditeur de journée en composables dédiés. +- Back : `campaigns` (2366) et `payments` (1376) → sous-modules de domaine (envoi, matching, suivi / facturation, rapprochement). + +### P2 — Fiabilité & vélocité +- **Tests** (vitest) sur les modules à risque : paiements, pont legacy, conformité adresses, roster solver. +- **CI/CD** : remplacer `scp + docker restart` manuel par un pipeline (Gitea Actions) build+déploiement+santé. +- **Observabilité closed-loop** : généraliser le pattern `_lastRun`/`/status` + réconciliation (déjà sur le + pont) aux jobs critiques ; clés Uptime-Kuma. + +--- + +## 5. Source de vérité — généralisation du pattern « Adresses » + +| Entité | Source de vérité | Lien stable | État | +|---|---|---|---| +| **Adresse** | `rqa_addresses` (RQA local) + `fiber_availability` | `aq_address_id` + `linked_address` | ✅ FAIT (16 561 liées ; page Conformité) | +| **Client** | ERPNext Customer | `legacy_account_id` | partiel (matching legacy) | +| **Appareil/CPE** | GenieACS (serial/MAC réels) | MAC ↔ Service Equipment | piège connu (TPLG vs serial réel) | +| **Service TV** | Ministra (SID = id ligne `service` legacy) | `legacy_activation_url` | read-only (pas de 2e chemin d'écriture) | +| **Facturation** | **Legacy (autoritaire)** jusqu'au cutover | — | scheduler ERPNext en pause | + +Prochaine application directe : finir le matching **Client** (legacy_account_id) et **Device** (MAC↔GenieACS) +sur le même modèle (résoudre une fois → persister → lire le lien). + +--- + +## 6. Roadmap par phases + +- **Phase 1 — Hygiène & fondations** : helpers partagés, code mort, `r.ok` partout, CI/CD minimal, tests des + modules critiques. *(Risque faible, gros gain de fiabilité.)* +- **Phase 2 — Modularisation** : déplacer hub `lib/*` → `lib//` ; décomposer les god-files front/back. +- **Phase 3 — Sources de vérité** : généraliser (Client, Device, Service) + observabilité closed-loop partout. +- **Phase 4 — Vision produit** : cutover facturation legacy→ERPNext ; app terrain Capacitor (GPS live des + unités, remplace le relevé manuel sur la carte) ; portail client self-service (abonnement + RDV + paiement). + +--- + +## 7. Métriques de succès +- 0 fichier > 800 lignes (front et back). +- 0 helper dupliqué (`norm`/`cors`/`geo`/`html` centralisés). +- 100 % des entités clés avec source de vérité + validation à la saisie + lien persisté. +- Déploiement par pipeline (0 `scp` manuel) ; tests verts sur les modules critiques. +- Tout job inter-systèmes : idempotent + observable + réconcilié (cf. ENGINEERING_PRACTICES). diff --git a/services/targo-hub/lib/address-conformity.js b/services/targo-hub/lib/address-conformity.js index c20d48a..bba7b9e 100644 --- a/services/targo-hub/lib/address-conformity.js +++ b/services/targo-hub/lib/address-conformity.js @@ -12,27 +12,8 @@ * * Tout est LOCAL (Postgres ERPNext). Routes sous /address/conformity/* (cf. server.js). */ -const { Pool } = require('pg') -const { json, parseBody, log } = require('./helpers') -const { searchRaw } = require('./address-db') - -let _pool -function pool () { - if (!_pool) { - _pool = new Pool({ - host: process.env.ADDR_DB_HOST || 'erpnext-db-1', port: +(process.env.ADDR_DB_PORT || 5432), - user: process.env.ADDR_DB_USER || 'postgres', password: process.env.ADDR_DB_PASS || '123', - database: process.env.ADDR_DB_NAME || '_eb65bdc0c4b1b2d6', max: 4, idleTimeoutMillis: 30000, - }) - _pool.on('error', e => log('address-conformity pool:', e.message)) - } - return _pool -} -function cors (res) { - res.setHeader('Access-Control-Allow-Origin', '*') - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') - res.setHeader('Access-Control-Allow-Headers', 'Content-Type') -} +const { json, parseBody, log, cors } = require('./helpers') +const { searchRaw, pool } = require('./address-db') // réutilise le pool LOCAL partagé (rqa_addresses + fiber) // Type d'adresse (pour trier la file) : camping (sobriquet de lot), civique à corriger, non-adresse, review standard. const TYPE_SQL = `CASE diff --git a/services/targo-hub/lib/address-validate.js b/services/targo-hub/lib/address-validate.js index a01eb07..228f8a4 100644 --- a/services/targo-hub/lib/address-validate.js +++ b/services/targo-hub/lib/address-validate.js @@ -17,19 +17,10 @@ // ./address-search.js — already used by the customer onboarding wizard. // We layer a confidence score + canonical formatting on top. -const { json, parseBody, log } = require('./helpers') +const { json, parseBody, log, cors } = require('./helpers') const { searchAddresses } = require('./address-search') const { searchLocal, searchRaw } = require('./address-db') -// CORS pour l'endpoint public /address/search (appelé par le site web depuis un autre domaine). -// Données publiques (adresses RQA + dispo fibre) → ouverture large, lecture seule. -function cors (res) { - res.setHeader('Access-Control-Allow-Origin', '*') - res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS') - res.setHeader('Access-Control-Allow-Headers', 'Content-Type') - res.setHeader('Cache-Control', 'public, max-age=60') -} - // Normalize for fuzzy comparison: lowercase, strip diacritics, collapse // whitespace, drop punctuation. Used to score how close a typed address // is to a RQA result. diff --git a/services/targo-hub/lib/helpers.js b/services/targo-hub/lib/helpers.js index 35fa31c..b9bb13b 100644 --- a/services/targo-hub/lib/helpers.js +++ b/services/targo-hub/lib/helpers.js @@ -133,8 +133,16 @@ function deepGetValue (obj, path) { return node?._value !== undefined ? node._value : null } +// CORS partagé pour les endpoints PUBLICS (données publiques, lecture seule + écritures internes). +// Pose les en-têtes via setHeader (fusionnés par writeHead de json()). methods optionnel. +function cors (res, methods = 'GET, POST, OPTIONS') { + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Access-Control-Allow-Methods', methods) + res.setHeader('Access-Control-Allow-Headers', 'Content-Type') +} + module.exports = { - log, json, parseBody, httpRequest, + log, json, parseBody, httpRequest, cors, erpFetch, erpRequest, lookupCustomerByPhone, createCommunication, nbiRequest, deepGetValue, }