# 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`.