Optimisation (consolidation helpers address) + doc Vision/modularisation
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) <noreply@anthropic.com>
This commit is contained in:
parent
27bbcf43d0
commit
f33f7a6309
129
docs/architecture/VISION.md
Normal file
129
docs/architecture/VISION.md
Normal file
|
|
@ -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/<domaine>/<module>.js` + `lib/<domaine>/index.js` (interface
|
||||
publique) ; `lib/util/` (norm, cors, geo, html) ; `server.js` route vers `require('./lib/<domaine>')`.
|
||||
|
||||
**Cible Ops** : `src/modules/<domaine>/` (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/<domaine>/` ; 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).
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user