gigafibre-fsm/docs/design/activation-billing-automation.md
louispaulb 512c4a5f1b feat(ops): dispatch auto complet + perf Boîte/rapports + fix session
Planificateur « Suggérer » : 4 stratégies (smart / meilleurs d'abord / équilibré /
juste ce qu'il faut), compétences+niveaux par tech (édition inline), niveau requis
par compétence + par job, carte des tournées (1 couleur/tech, domicile→arrêts,
sélecteur de jour), fenêtre de dispatch auj.+demain (dates sélectionnées), règle
week-end + placeholder « en attente du quart », clustering + lasso + filtre-date
sur la carte, accès rapide « À assigner » (badge).

Boîte : liste /conversations allégée (45 Mo → ~1 Mo, 17×) + messages chargés à
l'ouverture. Rapports : cache SWR sur revenue-explorer (22×). Session : keep-alive
+ timeout fetch global + authFetch durci → fin des rechargements manuels.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:49:35 -04:00

117 lines
7.0 KiB
Markdown

# Automatisation : Installation → Approbation → Facturation
> Statut : **PLAN** (2026-06-18). Touche F (live, autoritatif) → tout changement F = test-first.
> Décisions validées : (1) approbation **explicite dans Ops** ; (2) encaissement **immédiat à l'approbation**.
## Objectif
Chaîne **100 % automatisée après un seul point de contrôle humain** (l'approbation) :
installation complétée (tech) → **approuvée** (dispatcher/admin dans Ops) → facturation prorata
**consolidée, émise et encaissée immédiatement**, sans double-débit, F restant la source de vérité
de facturation.
## Principe de sécurité central — UN seul chargeur, DEUX déclencheurs
F ne charge pas « une facture » : `stripe_ppa.php` charge `sum(total_amt - billed_amt)` — **le reste
impayé, facture par facture**. Donc :
- **Déclencheur A — à l'approbation (immédiat)** : on charge le prorata off-session puis on pose
`billed_amt = total` sur cette facture.
- **Déclencheur B — balayage du 15 (`stripe_ppa.php`)** : charge le reste impayé ; le prorata déjà
réglé (`billed_amt = total`) contribue **0****jamais rechargé**.
Les deux écrivent le **même registre `billed_amt`** → le reste impayé d'une facture est chargé
exactement une fois. **Pas de double-débit par construction** — surtout ne PAS bâtir un 2ᵉ chargeur
OPS (cause de l'incident de juin 2026, cf `feedback_ppa_double_charge_incident`).
## Pipeline cible
```
[Tech] complète le job TERMINAL de la chaîne
│ (existant : setJobStatusWithChain → _isChainTerminal → activateSubscriptionForJob)
État « À approuver » (abonnements restent 'En attente' ; aperçu prorata calculé, AUCUNE écriture)
[Dispatcher/Admin] révise l'install + l'aperçu prorata → clique « Approuver la facturation »
▼ séquence atomique-idempotente (clé = job/approbation)
a. Construire le prorata consolidé (moteur : récurrent proraté, ponctuel/install plein,
rabais selon le type, arrondi plus-grand-reste)
b. Créer la facture dans F (table `invoice`, date_orig = date d'approbation) ← push-to-F
c. Charger immédiatement off-session via Stripe (clé d'idempotence = id facture)
d. Sur succès : poser billed_amt = total (règlement) → exclu du balayage du 15
e. Activer les abonnements (Actif, start_date = lendemain — jour d'activation gratuit)
f. Re-synchro miroir ERPNext
Récurrent : cycle normal de F (émission le 1er via task_charge_recurrent, balayage le 15)
```
## État actuel (acquis — P0)
- **Moteur** `services/targo-hub/lib/proration.js` : consolidation *type-aware* (récurrent proraté /
ponctuel-install plein / rabais selon le type), arrondi **plus-grand-reste** (somme exacte, 0 résidu),
écriture **brouillon double-gatée** (`commit:true` + env `PRORATION_WRITE=on`, OFF par défaut).
Endpoints `POST /billing/prorate-preview` et `POST /billing/activation-preview` (lecture seule).
- **dispatch.js** `activateSubscriptionForJob` : déjà branché sur le moteur → **UNE** facture
consolidée (au lieu d'une par abonnement) ; filtre strict `En attente` (ne re-facture jamais les
actifs) ; gaté `PRORATION_WRITE` (n'écrit rien tant que non armé).
- **F** : `stripe_ppa.php` (balayage du 15, `sum(total_amt-billed_amt)`), `task_charge_recurrent.php`
(émission le 1er, due_date 16), `payment_apply.php` / `credit_creance.php` (règlent `billed_amt`),
`pay_*`/`payment_add.php` (paiement d'une facture précise).
## Composants à bâtir
### A. Ops — la porte d'approbation
- Nouvel état sur `Dispatch Job` (ou doctype léger « Approbation Facturation ») : `À approuver``Approuvé`.
- Vue Ops : installs complétées en attente, avec **aperçu de la facture prorata** (via
`/billing/activation-preview`, lecture seule) — l'approbateur voit exactement ce qui sera facturé.
- Action « Approuver la facturation » (permission dispatcher/admin) → `POST` hub. Idempotente.
### B. Hub — l'orchestrateur
- `POST /billing/approve-activation { job | customer + service_location }` (staff-gated).
- Séquence b→f ci-dessus, **idempotente** (clé = job/approbation/mois), avec **reprise sur échec partiel**
(ex. facture créée mais charge échouée → reprend à la charge, ne recrée pas la facture).
### C. F — charge immédiate *scoped* (test-first)
- Réutiliser la logique **off-session** de `stripe_ppa.php` + le **règlement** de `payment_apply.php`,
mais **scoped à UNE facture** (le prorata) au lieu du solde complet.
- Clé d'idempotence Stripe par facture ; `billed_amt = total` + `billing_status = 1` sur succès.
### D. Push facture (hub/ERPNext → F)
- Le prorata DOIT exister dans la table `invoice` de F (sinon ni chargé, ni balayé). Relié au chantier
`project_sales_sync_f` (push-to-F). Reco : créer en ERPNext (déjà bâti) **puis pousser vers F**.
## Garde-fous (non négociables)
- **Idempotence** : clé Stripe par facture + reprise. (Leçon juin : charge OK mais règlement échoue
sous charge → recharge. Ici : clé idempotence + règlement fiable.)
- **`PRORATION_WRITE` off** jusqu'à validation ; brouillon → revue → armement.
- **Test-first** : Stripe **test mode** + client **C-LPB4**, puis **1 client réel**, puis rollout.
- **Pas de carte au dossier** → courriel (comme `stripe_ppa.php`), NE PAS marquer payé.
- **Échec de charge** → facture reste due (`billed_amt` inchangé) → rejoint le **balayage du 15** (filet)
+ alerte ; NE JAMAIS activer « payé » sans confirmation Stripe.
- **Dernier jour du mois** → prorata 0 (le récurrent du 1er couvre) — pas de facture prorata.
- **Ré-approbation / double-clic** → idempotent : ne pas créer 2 factures prorata pour la même
activation (clé = service_location + job + mois).
- **CRTC 2026-43** : frais d'installation facturables (exception) — la ligne installation au plein
est conforme (cf `project_crtc_2026_43`).
## Phasage (incrémental, réversible)
| Phase | Contenu | Risque facturation |
|---|---|---|
| **P0** ✅ | Moteur + facture consolidée brouillon (gaté, ERPNext) | nul |
| **P1** | Porte d'approbation Ops : état + **aperçu lecture seule** + action (sans facturer) | nul |
| **P2** | Push prorata → F (créer la facture dans F, **sans charger**) ; valider qu'elle serait balayée le 15 | faible (filet 15) |
| **P3** | Charge immédiate *scoped* dans F (test mode → C-LPB4 → 1 client) + règlement `billed_amt` + idempotence | contrôlé |
| **P4** | Câbler approbation → orchestrateur complet (auto) + monitoring/alertes | contrôlé |
| **P5** | Armer en prod (`PRORATION_WRITE=on`) après revue d'un cycle complet | go-live |
## Décisions ouvertes restantes
- **Locus de création de la facture** : F-direct vs ERPNext+push. Reco : ERPNext d'abord (déjà bâti) puis push-to-F.
- **Ce que l'approbateur révise** (checklist d'install, photos ?) — hors scope facturation pour l'instant.
- **Mapping client F↔ERPNext** pour le push (legacy_account_id / stripe_id) — à confirmer via `project_sales_sync_f`.