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

7.0 KiB

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 0jamais 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 ») : À approuverApprouvé.
  • 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.